Zetta v1.6

Components · App chrome

Header

The product top bar — brand or page title on the left, actions and the theme switcher on the right. A 56px sticky bar on the header surface with a hairline underline. This very page sits below the real one; the preview below is a static instance.

Preview

Zetta

Build with the skill

No package to install — hand this to your AI to generate or migrate the component in your stack.

Prompt for your AI
Apply the Zetta "Header" component to this project using the zetta-design-md skill.

WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `header` spec (variants: header, header-title, theme-button) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `header` spec in this project's stack.

AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.

ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility.

Anatomy & rules

┌─ header (56px) ────────────────────────────────────────────────────────────┐
│  [left slot]              [center slot]           [right slot]              │
│                                                                             │
│  Website:                 Nav links               Locale + CTA + Theme      │
│  Logo (mark + wordmark)   (up to 5 items)                                   │
│                                                                             │
│  In-app:                  —                       Search + Notif + Locale   │
│  Page title                                       + Theme + User avatar      │
│                                                                             │
│  Mobile (< 768px):        —                       Theme switcher            │
│  Hamburger + logo mark                                                       │
└─────────────────────────────────────────────────────────────────────────────┘

Parts:

  • header — sticky container, full viewport width, 56px height, z-index: 200
  • header-logo — mark + wordmark lockup; mark-only in collapsed sidebar state
  • header-title — in-app context only; current page name, single line, truncates
  • header-nav — horizontal nav links; website context only; collapses to drawer on mobile or when items exceed 5
  • header-actions — right slot flex row; hosts icon buttons and controls
  • header-locale-button — globe + locale code + chevron; opens locale dropdown
  • header-theme-button — sun/moon icon; toggles light/dark/accessibility theme
  • header-user-button — 32px avatar; opens user menu dropdown
  • header-user-dropdown — info header + action items

State Visual
Default Full header with nav, search, actions
Scrolled (sticky) Shadow appears below header as page scrolls
Mobile Collapsed nav — hamburger menu trigger visible
Mobile menu open Navigation items expand as drawer or dropdown

When to use

  • In every authenticated page view as the top-level navigation and identity surface.
  • In website/marketing pages as the primary brand bar and nav anchor.

When not to use

  • Do not render two headers in the same view.
  • Do not use the header as a section title inside a page — use {typography.heading-1} or {typography.heading-2} instead.
  • Do not put primary content actions in the header — those belong in the page-level action slot or sidebar.

Do

  • Keep the right slot to a maximum of 5 controls (search, notifications, locale, theme, user).
  • Show the logo mark only (no wordmark) on viewports below 768px.
  • Collapse website nav links to a drawer when items exceed 5 or viewport is below 768px.
  • Use header-nav-link-active state for the currently active route — apply via router.
  • Keep the page title (in-app) to a single line and truncate with ellipsis before it reaches the right slot.

Don't

  • Don't increase header height beyond 56px.
  • Don't put more than one CTA button in the website navbar right slot.
  • Don't render the full logo lockup (mark + wordmark) in the collapsed sidebar state or on mobile — use mark only.
  • Don't place destructive actions in the header.
  • Don't suppress the focus ring on any header interactive element.

  • Page title (in-app): Match the route or section name exactly. Sentence case. No verbs. Maximum 40 characters before truncation kicks in.
  • Nav links (website): Short nouns. Title case. 1–2 words maximum. ("Product", "Pricing", "Docs", "Blog").
  • CTA label (website): Action verb + object. ("Get started", "Sign in", "Try free"). Maximum 3 words.
  • Locale button label: 2-letter ISO 639-1 uppercase code only ("EN", "FR", "ID"). No flag emojis.
  • Locale dropdown items: Full language name in that language ("English", "Français", "Bahasa Indonesia").
  • User menu display name: Full name as stored in profile. Email shown below in muted style.

Keyboard

Key Behavior
Tab Move focus through header interactive elements (logo, nav links, search, actions)
Enter / Space Activate the focused link or button
Arrow Down When a locale or user dropdown is open, move focus to first item
Escape Close any open header dropdown or mobile menu, returning focus to the trigger

Accessibility

  • ARIA role: <header> element with role="banner". Nav links wrapped in <nav aria-label="Main navigation">.
  • Logo: aria-label="Zetta — home" on the logo link. If mark-only, the wordmark text is visually hidden but present for screen readers.
  • Theme button: aria-label="Switch to dark mode" / "Switch to light mode" — updates dynamically.
  • Locale button: aria-label="Select language". Active locale shown with aria-checked="true" on the dropdown item.
  • User button: aria-label="User menu" on the avatar trigger. aria-expanded reflects dropdown state.
  • Focus management: On dropdown open, focus moves to first menu item. On close (keyboard dismiss or click-outside), focus returns to trigger.
  • Skip link: A visually hidden "Skip to main content" link must appear as the first focusable element before the header — product-level responsibility.
  • Contrast: Logo mark (Lime #b6d600) on white canvas is 1.67:1 — permitted only under the WCAG logotype exemption, not as a UI boundary. Wordmark ({colors.ink}) meets 19.8:1.

Spec

Values and token references resolved straight from the Zetta spec.

header
backgroundColor
{colors.surface-header}
borderBottomColor
{colors.hairline}
borderBottomWidth
1px
height
56px
paddingX
{spacing.page}
display
flex
alignItems
center
justifyContent
space-between
position
sticky
top
0
zIndex
{elevation.z-index.navigation}
header-logo
display
flex
alignItems
center
gap
{spacing.sm}
markSize
24px
markColor
{colors.brand}
wordmarkColor
{colors.ink}
fontFamily
Inter
fontSize
16px
fontWeight
700
lineHeight
1.3
letterSpacing
-0.01em
textDecoration
none
header-logo-dark
markColor
{colors.brand}
wordmarkColor
{logo.on-dark-color}
header-title
textColor
{colors.ink}
fontFamily
Inter
fontSize
18px
fontWeight
600
lineHeight
1.35
letterSpacing
-0.01em
header-nav
display
flex
alignItems
center
gap
{spacing.lg}
header-nav-link
textColor
{colors.muted}
fontFamily
Geist
fontSize
14px
fontWeight
500
lineHeight
1.4
textDecoration
none
header-nav-link-hover
textColor
{colors.ink}
header-nav-link-active
textColor
{colors.ink}
fontWeight
600
header-nav-link-focus
outline
2px solid {colors.border-focus}
outlineOffset
2px
header-actions
display
flex
alignItems
center
gap
{spacing.sm}
header-theme-button
iconSize
{icons.sizeMap.md}
iconColor
{colors.muted}
header-theme-button-hover
iconColor
{colors.primary}
backgroundColor
{colors.primary-bg}
header-theme-button-active
backgroundColor
{colors.surface-pressed}
header-theme-button-focus
outline
2px solid {colors.border-focus}
outlineOffset
2px
header-locale-button
display
flex
alignItems
center
gap
{spacing.xs}
height
32px
paddingX
{spacing.sm}
borderRadius
{rounded.base}
backgroundColor
transparent
borderColor
transparent
borderWidth
1px
textColor
{colors.muted}
fontFamily
Geist
fontSize
14px
fontWeight
500
iconSize
{icons.sizeMap.md}
chevronSize
{icons.sizeMap.sm}
cursor
pointer
header-locale-button-hover
backgroundColor
{colors.primary-bg}
textColor
{colors.primary}
header-locale-button-active
backgroundColor
{colors.surface-pressed}
header-locale-button-focus
outline
2px solid {colors.border-focus}
outlineOffset
2px
header-locale-dropdown
minWidth
160px
header-user-button
width
32px
height
32px
borderRadius
{rounded.full}
cursor
pointer
header-user-button-focus
outline
2px solid {colors.border-focus}
outlineOffset
2px
header-user-dropdown
minWidth
200px
paddingY
{spacing.xs}
header-user-dropdown-header
paddingX
{spacing.md}
paddingY
{spacing.sm}
borderBottomColor
{colors.hairline}
borderBottomWidth
1px
nameColor
{colors.ink}
nameFont
Geist
nameFontSize
14px
nameFontWeight
500
emailColor
{colors.muted}
emailFont
Geist
emailFontSize
12px
emailFontWeight
400
header-cta
value
header-search-trigger
height
32px
paddingX
{spacing.md}
borderRadius
{rounded.base}
backgroundColor
{colors.surface-secondary}
borderColor
{colors.hairline}
borderWidth
1px
textColor
{colors.muted}
fontFamily
Geist
fontSize
13px
minWidth
200px
maxWidth
400px
display
flex
alignItems
center
gap
{spacing.sm}
header-search-trigger-hover
borderColor
{colors.border-strong}
backgroundColor
{colors.surface-card}
header-notification-trigger
position
relative
iconSize
{icons.sizeMap.md}
iconColor
{colors.muted}
borderRadius
{rounded.base}
padding
{spacing.xs}
cursor
pointer
header-notification-badge
position
absolute
top
2px
right
2px
minWidth
16px
height
16px
borderRadius
{rounded.full}
backgroundColor
{colors.danger}
textColor
{colors.danger-text}
fontFamily
Geist
fontSize
10px
fontWeight
700
display
flex
alignItems
center
justifyContent
center
paddingX
3px
borderColor
{colors.canvas}
borderWidth
2px
header-workspace-trigger
display
flex
alignItems
center
gap
{spacing.xs}
height
32px
paddingX
{spacing.sm}
borderRadius
{rounded.base}
fontFamily
Geist
fontSize
14px
fontWeight
500
textColor
{colors.ink}
cursor
pointer
borderColor
{colors.hairline}
borderWidth
1px
backgroundColor
transparent
header-workspace-trigger-hover
backgroundColor
{colors.surface-secondary}
header-mega-menu
position
fixed
top
56px
left
0
right
0
backgroundColor
{colors.surface-card}
borderBottomColor
{colors.hairline}
borderBottomWidth
1px
shadow
{elevation.shadow.overlay}
zIndex
{elevation.z-index.popup}
padding
{spacing.page}
display
grid
gridTemplateColumns
repeat(4, 1fr)
gap
{spacing.lg}
header-mega-menu-section-title
fontFamily
Geist
fontSize
11px
fontWeight
600
textColor
{colors.muted}
textTransform
uppercase
letterSpacing
0.06em
paddingBottom
{spacing.xs}
borderBottomColor
{colors.hairline}
borderBottomWidth
1px
marginBottom
{spacing.sm}

Known gaps

  • Sticky header scroll behavior — transparency / blur effect not yet specified.
  • Notification panel layout and content spec — product-defined; no additional token gaps.