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-stickyis a minimum: at 300% text, with the header wrapped to several rows, Safari can leave a field reached with Tab under it. Setscroll-padding-block-startonhtmlto your header's real height, or leave out.o-sticky.
Code
Copy the markup
import "@luz-ds/catalog/components/site-header.css";
<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">
<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:
<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.
| Class | Effect |
|---|---|
.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 | 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 | 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 | 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 (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-stickythe stickiness and the scroll padding,.o-container--inlinethe gutters,.o-rowthe wrapping row, and the navbar the links and their panel. - A one-row header is core's
--luz-bar-htall: 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-hfrom the top of the window. - With the modifier the brand is the publication's name:
--luz-font-headingat--luz-text-xl,--luz-text-lgat 640 px and below. - Without
@luz-ds/catalognothing 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 anaria-label("Main"). The button's name is "Menu"; the browser exposes its expanded state. The current link hasaria-current="page"and an underline. - Brand: a link, never a heading: the page's one
h1is its own title. A logo is an<img>whosealtis 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
--publicationfor 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
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.
| Part | Colour on background | Light | Dark | High contrast | Needs |
|---|---|---|---|---|---|
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- Layout and breakpoints
--luz-bar-h
Keyboard and ARIA are in the Code tab.