Skip to main content

Catalog · Navigation

Site header

Starts every page of a site with its name, its main links and a few controls, in one row that stays at the top.

Fits best: landing, blog — the top of every page of a landing site, a blog or a newspaper

Example

The header of a conference site, with a name, three links and a Log in link, and the same header for a blog, with its name as a publication's.

At 768 px of window width and below the links collapse into a menu, the catalog's navbar. Its panel opens under this example where the browser supports CSS anchor positioning, and under the top of the window, where a header sits, elsewhere.

The second header has .luz-site-header--publication: the name in the heading font, larger. The examples are not sticky: in a page, .o-sticky keeps the header at the top.

Known issues

The header's own issues are those of the pieces of core it is made of.

  • The menu panel. If you keep pressing Tab after the last link of the open menu, focus moves on into the page and the menu stays open over it; Escape closes it. Without CSS anchor positioning the panel and the language list open at the height of a one-row header from the top of the window, so with enlarged text they can cover part of the header. See the navbar's known issues.
  • Sticky header and enlarged text. The scroll padding of .o-sticky is a minimum: at 300% text, with the header wrapped to several rows, Safari can leave a field reached with Tab under it. Set scroll-padding-block-start on html to your header's real height, or leave out .o-sticky.

Code

Copy the markup

Bundler
import "@luz-ds/catalog/components/site-header.css";
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/catalog@0.3.0/dist/components/site-header.min.css" integrity="sha384-21PMBkvzBZetFOsMonT4oc4Ak41zobEeRyD0yfqjWd9UWhrz0PEpMPo/3hyvH1BH" crossorigin="anonymous">
HTML
<header class="luz-site-header o-sticky">
  <div class="o-container o-container--inline o-row">
    <button class="luz-nav-toggle" type="button" popovertarget="site-nav" aria-label="Menu">
      <svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h16"/></g></svg>
    </button>
    <a class="luz-site-header__brand" href="/">Acme Summit</a>
    <nav class="luz-navbar" id="site-nav" aria-label="Main" popover>
      <ul class="luz-navbar__list">
        <li><a class="luz-navbar__link" href="/programme/" aria-current="page">Programme</a></li>
        <li><a class="luz-navbar__link" href="/speakers/">Speakers</a></li>
        <li><a class="luz-navbar__link" href="/tickets/">Tickets</a></li>
      </ul>
    </nav>
    <div class="o-row luz-site-header__actions">
      <a class="luz-btn luz-btn--ghost" href="/account/">Log in</a>
    </div>
  </div>
</header>

A blog or a newspaper adds the modifier, and catalog's search and language switch go in the actions:

HTML
<header class="luz-site-header luz-site-header--publication o-sticky">
  <div class="o-container o-container--inline o-row">
    <button class="luz-nav-toggle" type="button" popovertarget="site-nav" aria-label="Sections">
      <svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h16"/></g></svg>
    </button>
    <a class="luz-site-header__brand" href="/">The Vejer Review</a>
    <nav class="luz-navbar" id="site-nav" aria-label="Sections" popover>
      <ul class="luz-navbar__list">
        <li><a class="luz-navbar__link" href="/news/" aria-current="true">News</a></li>
        <li><a class="luz-navbar__link" href="/culture/">Culture</a></li>
        <li><a class="luz-navbar__link" href="/opinion/">Opinion</a></li>
      </ul>
    </nav>
    <div class="o-row luz-site-header__actions">
      <luz-search>…</luz-search>
      <luz-lang-switch>…</luz-lang-switch>
    </div>
  </div>
</header>

Variants and options

One modifier. The header is a class of catalog on a native <header>, laid out by core's objects in the markup.

ClassEffect
.luz-site-header
On <header>: the page surface, a rule underneath, the gaps of its row, and the anchor its panels open under
.luz-site-header--publication
For a blog or a newspaper: the brand is the publication's name, in the heading font, larger, and underlined on hover; at 768 px and below it fills the room between the menu button and the actions and wraps there
.luz-site-header__brand
The site's name as a link to the home page: semibold, not underlined, at least 44 px tall; it takes the free room of the row. A logo <img> inside it is 44 px tall
.luz-site-header__actions
On a .o-row at the end of the row: a search, a language switch, a button
.o-sticky (core)
Keeps the header at the top while the page scrolls, in a window at least 30 rem tall; see the page and its bars
.o-container.o-container--inline.o-row (core)
The row: the width and gutters of the page, wrapping rather than overflowing
.luz-nav-toggle, .luz-navbar (catalog)
The menu button and the links: see the navbar

How it works

CSS from components/site-header.css of @luz-ds/catalog on core's objects and the catalog's navbar. No JavaScript.

  • The header adds the skin only: the page surface, the rule underneath, the brand, and the gaps of the row (8 px between rows, 16 px between items, 24 px from 769 px). Core gives the rest: .o-sticky the stickiness and the scroll padding, .o-container--inline the gutters, .o-row the wrapping row, and the navbar the links and their panel.
  • A one-row header is core's --luz-bar-h tall: 44 px controls, 4 px above and below, and the 1 px rule. With enlarged text the row wraps and the header grows.
  • The header names itself the anchor --luz-bar, scoped to itself. Where the browser supports CSS anchor positioning, the navbar's panel and the list of the catalog's <luz-lang-switch> open under it on one row or on two. Elsewhere they open at --luz-bar-h from the top of the window.
  • With the modifier the brand is the publication's name: --luz-font-heading at --luz-text-xl, --luz-text-lg at 640 px and below.
  • Without @luz-ds/catalog nothing changes: its rules that refine the catalog's elements match nothing.

Without JavaScript

Nothing changes: the menu is a popover opened by the button's popovertarget, with no script, and the links are plain links.

Keyboard and ARIA

  • Order: the menu button, the brand, the links, then the actions, in the order of the source. With the menu open, the next Tab after the button goes into the panel's links.
  • Keys: Enter or Space on the button opens and closes the panel; Escape closes it and returns focus to the button.
  • Names: the <nav> has an aria-label ("Main"). The button's name is "Menu"; the browser exposes its expanded state. The current link has aria-current="page" and an underline.
  • Brand: a link, never a heading: the page's one h1 is its own title. A logo is an <img> whose alt is the name, with no text beside it.
  • Landmarks: a <header> that is a direct child of <body> is the banner. Put the skip link before it.

Contrast and focus are in the Design tab.

Design

When to use

Use the header at the top of every page of a landing site, a blog or a newspaper: the visitor sees which site this is and reaches its main parts from any page.

  • The plain header for a landing site, a product or an event.
  • With --publication for a blog or a newspaper; for the front page of a newspaper, where the name and the date set the tone, use the masthead.

When not to use

Do not use it in an app: the app bar is the bar of the main area beside the sidebar.

  • One header per page, never inside a component: it is the page's banner.
  • A single page that fits one screen needs a heading and a skip link, not a header.

Do and don't

Do Three to six links, each a noun that names its part of the site, with the current one marked.
Don't A dozen links, some of them vague: the row wraps into a wall and the visitor reads every word to choose.

Writing the content

The brand is the site's name; the links are the names of its main parts.

  • Brand: the name as people say it, one to three words; a publication's in full, as it appears on the publication. It wraps at a narrow width; never shorten it with an ellipsis. A logo is decorative: the name stays as text in the link.
  • Links: three to six, one or two words each, the same words as the titles of the pages they go to. No "Home" beside a brand that already goes home.
  • Actions: a verb that says what happens, such as "Log in". Menu button: "Menu", translated like the rest of the page.

Contrast and focus

Text sits on the page surface, in the row and in the open panel. The scrim starts under the header, so the header is never dimmed.

PartColour on backgroundLightDarkHigh contrastNeeds
Brand and current link
--luz-text-primary on --luz-surface-page
15.8:1
15.1:1
21.0:1
4.5:1
Links
--luz-text-secondary on --luz-surface-page
6.2:1
7.5:1
19.3:1
4.5:1
Focus ring
--luz-focus-ring on --luz-surface-page
5.6:1
7.7:1
21.0:1
3:1

Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings, borders and icons.

  • Focus: every link and button in the header keeps core's ring, 2 px of --luz-focus-ring. The current link has an underline as well as its colour, so the mark does not depend on colour.
  • Edges: the rule under the header (--luz-divider) is decorative and has no minimum; forced colours keep it.

Tokens it uses

The 14 tokens that the .luz-site-header rules of catalog’s components/site-header.css read, by family. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.

Spacing and sizing
--luz-control-lg, --luz-space-2xs, --luz-space-lg, --luz-space-md, --luz-space-xs
Typography
--luz-font-heading, --luz-font-sans, --luz-text-base, --luz-text-lg, --luz-text-xl

Keyboard and ARIA are in the Code tab.