Standard (default — Linear-style)

Inline (Stripe-style — nav + actions right)

Breadcrumb (Notion-style — breadcrumb in __nav slot)

Centered (Apple-style — logo at center)

Sticky variant (data-sticky="true")

Scroll this section — the sticky header above stays pinned.

Long content fills space to demonstrate the sticky behavior...

Menu


  
/*
 * Site-header — section
 *
 * Top-of-page chrome. Four patterns via data-pattern attribute:
 *
 *   standard   — Linear-style: logo left, nav center, actions right (default)
 *   inline     — Stripe-style: logo left, nav + actions right
 *   breadcrumb — Notion-style: logo left, breadcrumb (in __nav slot) grows,
 *                actions right
 *   centered   — Apple-style: logo centered, nav left of logo, actions right
 *
 * Sticky behavior is opt-in via data-sticky="true". Mobile collapse is
 * container-query driven: below the mobile breakpoint, nav + actions
 * hide and the mobile-trigger button appears.
 *
 *   <header class="site-header" data-pattern="standard" data-sticky="true">
 *     <a class="site-header__logo" href="/">Logo</a>
 *     <nav class="site-header__nav" aria-label="Primary">
 *       <a href="/docs">Docs</a>
 *     </nav>
 *     <div class="site-header__actions">
 *       <button class="button -primary -s">Get started</button>
 *     </div>
 *     <button class="site-header__mobile-trigger" type="button"
 *             aria-haspopup="dialog" aria-controls="mobile-menu"
 *             aria-expanded="false" aria-label="Open menu">☰</button>
 *   </header>
 */

@layer malevich.components {
  .site-header {
    container-type: inline-size;
    container-name: site-header;

    display: grid;
    align-items: center;
    gap: var(--space-gap-elements-m);

    padding-block: var(--space-inset-block-m);
    padding-inline: var(--space-inset-section-m);

    background-color: var(--color-surface-canvas);
    border-block-end: var(--border-width-hairline) solid var(--color-border-muted);

    inline-size: 100%;
    box-sizing: border-box;

    /* Default layout = standard pattern */
    grid-template-columns: auto 1fr auto auto;
    grid-template-areas: "logo nav actions mobile";
  }

  /* Sticky behavior modifier */
  .site-header[data-sticky="true"] {
    position: sticky;
    inset-block-start: 0;
    z-index: 100;
    backdrop-filter: blur(var(--site-header-sticky-blur));
  }

  /* ---- Grid areas (common to all patterns) ---- */

  .site-header__logo {
    grid-area: logo;
    display: inline-flex;
    align-items: center;
    color: var(--color-ink-strong);
    text-decoration: none;

    font-family: var(--font-display-title-family);
    font-size: var(--font-heading-section-size);
    font-weight: var(--font-display-title-weight);
    line-height: var(--font-heading-section-line-height);
    letter-spacing: var(--font-display-title-letter-spacing);
  }

  .site-header__nav {
    grid-area: nav;
    display: inline-flex;
    align-items: center;
    gap: var(--space-gap-elements-m);
    min-inline-size: 0;
  }

  .site-header__nav a {
    color: var(--color-ink-regular);
    text-decoration: none;
    padding-block: var(--space-inset-element-s);
    padding-inline: var(--space-inset-element-s);
    border-radius: var(--radius-button);
    white-space: nowrap;
    transition: color var(--motion-fast) var(--motion-easing-default),
                background-color var(--motion-fast) var(--motion-easing-default);

    font-family: var(--font-body-support-family);
    font-size: var(--font-body-support-size);
    font-weight: 500;
    line-height: var(--font-body-support-line-height);
    letter-spacing: var(--font-body-support-letter-spacing);
  }

  .site-header__nav a:hover {
    color: var(--color-ink-strong);
    background-color: var(--color-surface-raised);
  }

  .site-header__nav a[aria-current="page"] {
    color: var(--color-ink-strong);
    font-weight: 600;
  }

  .site-header__actions {
    grid-area: actions;
    display: inline-flex;
    align-items: center;
    gap: var(--space-gap-elements-s);
  }

  .site-header__mobile-trigger {
    grid-area: mobile;
    display: none;
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    border: 0;
    padding: var(--space-inset-element-s);
    margin: 0;
    cursor: pointer;
    color: var(--color-ink-strong);
    inline-size: var(--size-control-m);
    block-size: var(--size-control-m);
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-button);
  }

  .site-header__mobile-trigger:hover {
    background-color: var(--color-surface-raised);
  }

  /* ---- Pattern: standard (default, Linear-style) ----
   * logo | nav (center) | actions | mobile
   */
  .site-header[data-pattern="standard"],
  .site-header:not([data-pattern]) {
    grid-template-columns: auto 1fr auto auto;
    grid-template-areas: "logo nav actions mobile";
  }
  .site-header[data-pattern="standard"] .site-header__nav,
  .site-header:not([data-pattern]) .site-header__nav {
    justify-self: center;
  }

  /* ---- Pattern: inline (Stripe-style) ----
   * logo | spacer | nav | actions | mobile
   */
  .site-header[data-pattern="inline"] {
    grid-template-columns: auto 1fr auto auto auto;
    grid-template-areas: "logo . nav actions mobile";
  }
  .site-header[data-pattern="inline"] .site-header__nav {
    justify-self: end;
  }

  /* ---- Pattern: breadcrumb (Notion-style) ----
   * logo | breadcrumb (in __nav, grows from start) | actions | mobile
   * Authors place a <nav class="breadcrumb"> inside .site-header__nav.
   */
  .site-header[data-pattern="breadcrumb"] {
    grid-template-columns: auto 1fr auto auto;
    grid-template-areas: "logo nav actions mobile";
  }
  .site-header[data-pattern="breadcrumb"] .site-header__nav {
    justify-self: start;
    inline-size: 100%;
    overflow: hidden;
  }

  /* ---- Pattern: centered (Apple-style) ----
   * nav (left of logo) | logo (center) | actions (right) | mobile
   */
  .site-header[data-pattern="centered"] {
    grid-template-columns: 1fr auto 1fr auto;
    grid-template-areas: "nav logo actions mobile";
  }
  .site-header[data-pattern="centered"] .site-header__logo {
    justify-self: center;
  }
  .site-header[data-pattern="centered"] .site-header__nav {
    justify-self: start;
  }
  .site-header[data-pattern="centered"] .site-header__actions {
    justify-self: end;
  }

  /* ---- Container-aware mobile collapse ----
   * Below the mobile breakpoint every pattern reduces to:
   *   logo | spacer | mobile-trigger
   * Nav and actions are hidden; the consumer opens a Sheet from the
   * trigger.
   */
  @container site-header (max-width: 640px) {
    .site-header,
    .site-header[data-pattern] {
      grid-template-columns: auto 1fr auto;
      grid-template-areas: "logo . mobile";
    }
    .site-header__nav,
    .site-header__actions {
      display: none;
    }
    .site-header__mobile-trigger {
      display: inline-flex;
    }
  }
}
This component is pure CSS — no JavaScript required.
# Site-Header — AGENTS.md

> Auto-generated from `site-header.docs.md` + the modifier applicability matrix.
> Edit those source files; this file regenerates on build.

## Identity

- **Component:** `site-header`
- **Layer:** sections
- **Status:** stable
- **Last updated:** 2026-05-19

## Summary

(no summary)

## Variants

| data-pattern | Layout | Use for |
|---|---|---|
| `standard` (default) | logo \| nav (center) \| actions \| mobile | Marketing sites (Linear-style) |
| `inline` | logo \| · \| nav \| actions \| mobile | Product sites with right-aligned nav (Stripe-style) |
| `breadcrumb` | logo \| breadcrumb-in-__nav \| actions \| mobile | App chrome (Notion-style) |
| `centered` | nav \| logo (center) \| actions \| mobile | Retail / brand sites (Apple-style) |

## Modifier applicability

Per ADR-0007. Modifiers attach via `data-{category}="value"` attributes.

| Category | Accepts |
|---|---|
| `background` | `solid`, `gradient` |
| `surface` | `flat`, `elevated` |
| `effect` | `blur` |
| `shader` | — |
| `rhythm` | `compact`, `regular` |
| `motion` | any value |
| `behavior` | `sticky` |

## Anatomy

```html
<header class="site-header" data-pattern="standard" data-sticky="true">
  <a class="site-header__logo" href="/">Malevich</a>

  <nav class="site-header__nav" aria-label="Primary">
    <a href="/docs">Docs</a>
    <a href="/components">Components</a>
    <a href="/blog">Blog</a>
  </nav>

  <div class="site-header__actions">
    <button class="button -secondary -s">Sign in</button>
    <button class="button -primary -s">Get started</button>
  </div>

  <button class="site-header__mobile-trigger" type="button"
          aria-haspopup="dialog" aria-controls="mobile-menu"
          aria-expanded="false" aria-label="Open menu">
    ☰
  </button>
</header>

<dialog class="sheet" data-side="left" id="mobile-menu">
  <header class="sheet__header">
    <h2>Menu</h2>
    <button class="sheet__close" aria-label="Close"
            onclick="this.closest('dialog').close()">×</button>
  </header>
  <div class="sheet__body">
    <nav aria-label="Primary mobile">
      <a href="/docs">Docs</a>
      <!-- … -->
    </nav>
  </div>
</dialog>
```

The mobile-trigger button uses the same `aria-haspopup="dialog"` +
`aria-controls` contract as Popover, so the Popover runtime
auto-registers and wires up open/close + aria-expanded sync.

## Tokens consumed

- `--color-surface-canvas` — background
- `--color-border-muted` — bottom border
- `--color-ink-strong`, `--color-ink-regular`
- `--color-surface-raised` — nav link hover
- `--space-inset-block-m`, `--space-inset-section-m`
- `--space-gap-elements-{s,m}`
- `--radius-button`
- `--border-width-hairline`
- `--font-display-title-*`, `--font-heading-section-*`, `--font-body-support-*`
- `--motion-fast`, `--motion-easing-default`
- `--size-control-m`

### Component-tier (generated)
- `--site-header-mobile-breakpoint` (640px) — container-query threshold
- `--site-header-sticky-blur` (8px) — backdrop blur radius

## Accessibility contract

- Use `<header>` as the wrapper for the page-level header.
- `<nav aria-label="Primary">` identifies the navigation landmark.
- Mobile trigger has `aria-haspopup="dialog"` + `aria-controls` and
  the linked Sheet is a native `<dialog>` (so focus management,
  Escape close, and inert background come for free).
- Use `aria-current="page"` on the active nav link.

## Guidance

### Do

- Use real `<header>` and `<nav>` semantics.
- Pair the mobile trigger with a Sheet for the mobile menu.
- Set `aria-current="page"` on the active nav link.

### Don't

- Don't omit `aria-label` from the nav.
- Don't write breakpoint-specific CSS in user stylesheets — the
  container query handles the collapse.