Contact Us

Header & Navigation

DOC-00011 reference implementor, developer

Overview

The header system is composed of several coordinating components. All components are CORE-OWNED unless noted.

Who this is for

  • Implementors configuring site header layout, logo, and CTA buttons
  • Developers modifying header component behavior or adding new header controls
ComponentLocationPurpose
Header.astrosrc/core/components/nav/Site header shell — composes all controls
MainNav.astrosrc/core/components/nav/Desktop primary navigation with submenu support
MobileMenu.astrosrc/core/components/nav/Mobile navigation panel using Drawer
HamburgerButton.astrosrc/core/components/nav/Accessible mobile menu toggle
SiteLogo.astrosrc/core/components/nav/Config-driven logo with reactive theme switching
ThemeSwitcher.astrosrc/core/components/nav/Config-driven wrapper for theme variants
ThemeIconSwitcher.astrosrc/core/components/nav/Icon-cycling theme button
ThemeModeSwitcher.astrosrc/core/components/nav/ListBox-based theme select
SearchTrigger.astrosrc/core/components/search/Forward-compatible search entry point
Drawer.astrosrc/core/components/primitives/Reusable slide-in overlay primitive

Header.astro renders the site navigation bar. The navigation landmark is provided by the <nav> element inside MainNav. The banner landmark lives on the subheader slot wrapper in PageGridLayout, not on this component.

Composition:

  • SkipLink — first child
  • SiteLogo — site identity
  • MainNav — desktop navigation (hidden on mobile)
  • SearchTrigger — search icon button (hidden on mobile, lives in mobile panel)
  • ThemeSwitcher — theme controls (hidden on mobile, lives in mobile panel)
  • CTA Button — optional, configured via siteConfig.header.cta
  • HamburgerButton — mobile menu toggle (hidden on desktop)
  • MobileMenu — mobile navigation panel

Configuration:

  • siteConfig.header.stickyHeader'none' | 'sticky' | 'scroll-up' (default: 'scroll-up'):
    • 'none' — default block flow; header scrolls with the page
    • 'sticky'position: sticky; top: 0; header sticks when it reaches the viewport top
    • 'scroll-up'position: fixed; top: 0; header hides when the user scrolls down past 50px and reappears when they scroll up. A vanilla JS initializer sets --header-height on <html> so the grid row maintains its height. CSS transition respects prefers-reduced-motion.
  • siteConfig.header.navPosition'left' | 'center' | 'right', controls MainNav placement
  • siteConfig.header.mobileMenuPosition'left' | 'right' | 'full-width', controls which side the mobile drawer slides in from (default: 'right')
  • siteConfig.header.cta — optional CTA button ({ label, href, variant? })

--header-height CSS variable:

When stickyHeader: 'scroll-up' is active, Header.astro sets --header-height on <html> from the rendered header height on mount. This variable is consumed by PageGridLayout’s [data-area="header"] grid item to maintain its height while the fixed header overlays it. It is also available for use in other layout contexts that need to account for the header offset.

PageGridLayout integration:

PageGridLayout renders <Header /> by default. Pages can:

  • Default: Header renders automatically
  • Override: Pass a component to the header named slot
  • Suppress: Pass showHeader={false}

MainNav.astro renders the desktop primary navigation from menuConfig.mainNav.

  • Top-level items with children render a submenu via Dropdown.astro
  • aria-current="page" on the active top-level item
  • data-active on parent items when a child route is active
  • Submenu opens on click/Enter/Space; closes on ESC with focus return
  • Arrow key navigation within submenus
  • Dropdown.astro consumes the nav dropdown semantic surface for its panel (--st-color-nav-dropdown-bg / --st-color-nav-dropdown-border)

MobileMenu & HamburgerButton

MobileMenu.astro uses Drawer.astro. The drawer position is controlled by siteConfig.header.mobileMenuPosition ('left' | 'right' | 'full-width', default 'right'). Contains:

  • Navigation links (same data as MainNav, vertical layout)
  • SearchTrigger (when search is enabled)
  • ThemeSwitcher (when theme switcher is not 'none')
  • Drawer.astro uses the mobile navigation semantic surface (--st-color-nav-mobile-bg) and the shared overlay scrim token for the backdrop

The CTA button is not duplicated in the mobile panel — it stays in the main header bar.

HamburgerButton.astro toggles the mobile menu. It renders a <button> with the bars.svg icon, aria-expanded, and aria-controls pointing to the mobile panel ID.

The shared state ({ mobileOpen: false }) lives on Header.astro’s root wrapper, managed by a vanilla TypeScript controller.

SiteLogo.astro accepts a location: 'header' | 'footer' prop and renders the logo from siteConfig.branding.

Fallback chain:

  1. logoMain present — renders <img> with the URL resolved from logoUsageMatrix[location][currentTheme]
  2. logoMain absent — renders logoText (or siteName if logoText is unset) as a <span>

Props:

  • location'header' | 'footer' (required)
  • variant? — explicit LogoVariant override, takes precedence over logoUsageMatrix (REQ-00157)
  • alt? — override alt text; defaults to logoText, then siteConfig.siteName (REQ-00158)

Reactive theme switching: Vanilla JS maintains a currentTheme variable initialised from data-theme on mount. When theme-changed fires, currentTheme is updated from event.detail.theme, triggering an immediate <img src> swap without a page reload.

Logo asset pattern: logoMain and logoAlt are resolved URL strings imported in site.ts via Vite’s ?url suffix. See Site Configuration — Branding & Logos for the full setup pattern.

ThemeSwitcher

Three components work together:

  • ThemeSwitcher.astro — reads siteConfig.theme.themeSwitcher ('icon' | 'select' | 'none') and renders the appropriate variant
  • ThemeIconSwitcher.astro — cycles system → light → dark → system on click (REQ-00162)
  • ThemeModeSwitcher.astro — ListBox-based dropdown select

Shared storage contract: Both variants use setTheme(mode) and getTheme() from src/lib/theme.ts. The theme-changed custom event on window synchronizes all instances.

Multi-instance sync: Desktop and mobile instances listen for theme-changed events and update their displayed state without polling.

SearchTrigger

SearchTrigger.astro renders an icon button with data-search-trigger as a forward-compatible hook.

Behavior modes:

  • siteConfig.header.search.behavior = 'modal' — renders a <button>; clicking does nothing until the search modal is implemented
  • siteConfig.header.search.behavior = 'page' — renders an <a> linking to siteConfig.header.search.href
  • siteConfig.header.search.enabled = false — suppresses the trigger entirely

Drawer

Drawer.astro is a reusable slide-in overlay primitive in src/core/components/primitives/.

Props:

  • id — required, anchors aria-controls
  • position?'left' | 'right' | 'full-width' (default: 'right')
  • binding? — variable name controlling open/close (default: 'open')
  • label?aria-label for the dialog region

Features:

  • Focus trap via vanilla JS keyboard handling
  • Backdrop overlay click closes the panel
  • ESC closes the panel
  • Scroll lock on <body> while open
  • CSS transitions with prefers-reduced-motion support

HeaderConfig Schema

Defined in src/core/config/site.schema.ts:

type HeaderCtaConfig = {
  label: string;
  href: string;
  variant?: "primary" | "secondary";
};

type HeaderSearchConfig = {
  enabled: boolean;
  behavior: "modal" | "page";
  href?: string;
};

type HeaderConfig = {
  navPosition: "left" | "center" | "right";
  stickyHeader: "none" | "sticky" | "scroll-up";
  mobileMenuPosition: "left" | "right" | "full-width";
  search: HeaderSearchConfig;
  cta?: HeaderCtaConfig;
};

Defaults: navPosition: 'right', stickyHeader: 'scroll-up', mobileMenuPosition: 'right', search: { enabled: true, behavior: 'modal' }, cta: undefined.

lib/theme.ts

src/lib/theme.ts exports setTheme(mode) and getTheme(). Imported directly by vanilla TypeScript controllers that manage theme switching.

  • setTheme(mode) — writes localStorage['theme-preference'], sets data-theme on <html>, dispatches theme-changed event
  • getTheme() — reads from localStorage, returns 'system' when absent

Manual accessibility test entries verified during the accessibility audit. Covers keyboard operability, screen-reader announcements, and ARIA semantics.

Drawer

Interaction Expected Behavior WCAG Criterion Test Method
Tab to a Drawer trigger button, press Enter to openDrawer opens, focus moves into the drawer, focus is trapped inside the drawer2.1.1 Keyboard Keyboard
Press Escape while the Drawer is openDrawer closes, focus returns to the trigger button that opened it2.1.1 Keyboard Keyboard
Click outside the Drawer overlayDrawer closes, focus returns to the trigger button2.1.1 Keyboard Keyboard
Open the Drawer and inspect its containerDrawer has role="dialog", aria-modal="true", and an accessible name via aria-label or aria-labelledby4.1.2 Name, Role, Value Visual Inspection
Open the Drawer and scroll the background pageBackground scroll is locked while the Drawer is open; page does not scroll behind the overlay2.1.1 Keyboard Keyboard
Open the Drawer with prefers-reduced-motion: reduce enabledSlide/fade transitions are suppressed or instantaneous; drawer still functions2.3.3 Animation from Interactions Visual Inspection
Open the Drawer with NVDA runningNVDA announces the drawer as a dialog with its name4.1.2 Name, Role, Value Screen Reader

Header

Interaction Expected Behavior WCAG Criterion Test Method
Scroll down to trigger sticky header, Tab to header controlsHeader remains visible and functional in sticky state; all controls are keyboard-reachable2.1.1 Keyboard Keyboard
Inspect the header element in DevToolsHeader is a <header> element (implicit banner landmark); no ARIA attributes are lost during scroll state transition1.3.1 Info and Relationships Visual Inspection
Navigate header controls with NVDA in sticky stateNVDA announces header controls correctly in both normal and sticky states4.1.2 Name, Role, Value Screen Reader

MainNav

Interaction Expected Behavior WCAG Criterion Test Method
Tab through the main navigation linksAll nav links are focusable in logical order with visible focus rings2.1.1 Keyboard Keyboard
Press Enter on a navigation linkNavigation occurs; current page indicator is present on the active link2.1.1 Keyboard Keyboard
Inspect the navigation element in DevToolsNavigation is in a nav element with aria-label; active link has aria-current="page"; links are in a semantic list1.3.1 Info and Relationships Visual Inspection
Navigate the main nav with NVDA runningNVDA announces each link's text and role4.1.2 Name, Role, Value Screen Reader

Dropdown

Interaction Expected Behavior WCAG Criterion Test Method
Tab to a nav item with a dropdown, press Enter or Space to openDropdown opens; focus moves into the dropdown or remains on the trigger with arrow navigation available2.1.1 Keyboard Keyboard
Use Arrow Down/Up to navigate dropdown itemsFocus moves between items with visible focus ring on each2.1.1 Keyboard Keyboard
Press Escape while the dropdown is openDropdown closes, focus returns to the trigger2.1.1 Keyboard Keyboard
Inspect the dropdown trigger and menu in DevToolsTrigger has aria-expanded toggling; aria-haspopup is present; dropdown items have appropriate roles4.1.2 Name, Role, Value Visual Inspection
Open/close the dropdown with prefers-reduced-motion: reduce enabledAny open/close animation is suppressed or instantaneous2.3.3 Animation from Interactions Visual Inspection
Open the dropdown with NVDA runningNVDA announces the expanded/collapsed state and each item's label4.1.2 Name, Role, Value Screen Reader

HamburgerButton

Interaction Expected Behavior WCAG Criterion Test Method
At mobile viewport, Tab to the hamburger buttonButton is focusable with visible focus ring2.4.7 Focus Visible Keyboard
Press Enter on the hamburger buttonMobile menu opens2.1.1 Keyboard Keyboard
Press Enter again (or Escape) to close the menuMobile menu closes, focus remains on or returns to the hamburger button2.1.1 Keyboard Keyboard
Inspect the hamburger button in DevToolsButton has an accessible name via aria-label; aria-expanded toggles correctly; aria-controls references the menu4.1.2 Name, Role, Value Visual Inspection
Toggle the hamburger button with NVDA runningNVDA announces the button label (e.g., "Menu") and expanded/collapsed state4.1.2 Name, Role, Value Screen Reader

MobileMenu

Interaction Expected Behavior WCAG Criterion Test Method
Open the mobile menu via the hamburger buttonMenu opens, focus moves into the menu, focus is trapped inside2.1.1 Keyboard Keyboard
Tab through all mobile menu links and controlsAll items are focusable in logical order with visible focus rings2.1.1 Keyboard Keyboard
Press Escape while the mobile menu is openMenu closes, focus returns to the hamburger button2.1.1 Keyboard Keyboard
Inspect the mobile menu container in DevToolsMenu is rendered via Drawer (inherits role="dialog", aria-modal="true"); navigation links are in a semantic list; menu has an accessible name4.1.2 Name, Role, Value Visual Inspection
Open/close the mobile menu with prefers-reduced-motion: reduce enabledSlide/fade transitions are suppressed or instantaneous2.3.3 Animation from Interactions Visual Inspection
Open the mobile menu with NVDA runningNVDA announces the menu or dialog context4.1.2 Name, Role, Value Screen Reader

ThemeSwitcher

Interaction Expected Behavior WCAG Criterion Test Method
Tab to the theme switcher controlControl is focusable with visible focus ring2.4.7 Focus Visible Keyboard
Activate the theme switcher (Enter, Space, or click)Theme changes visually (light/dark or cycling)2.1.1 Keyboard Keyboard
Inspect the theme switcher wrapper in DevToolsActive variant component is rendered; inactive variant is not in DOM or is inert; no orphaned ARIA attributes4.1.2 Name, Role, Value Visual Inspection
Activate the theme switcher with NVDA runningNVDA announces the current state/label and the new theme state after activation4.1.2 Name, Role, Value Screen Reader

ThemeIconSwitcher

Interaction Expected Behavior WCAG Criterion Test Method
Tab to the icon theme switcher buttonButton is focusable with visible focus ring2.4.7 Focus Visible Keyboard
Press Enter or Space to cycle through theme optionsTheme changes, icon updates to reflect the new theme2.1.1 Keyboard Keyboard
Inspect the icon switcher button in DevToolsaria-label updates dynamically as theme changes; icon is decorative (aria-hidden="true")4.1.2 Name, Role, Value Visual Inspection
Cycle themes with NVDA runningNVDA announces the button label (e.g., "Switch to dark mode") and updated label after activation4.1.2 Name, Role, Value Screen Reader

ThemeModeSwitcher

Interaction Expected Behavior WCAG Criterion Test Method
Tab to the mode switcher (ListBox-based control)Control is focusable with visible focus ring2.4.7 Focus Visible Keyboard
Open the selector, use Arrow keys to navigate options, select an optionOptions are navigable by keyboard; theme changes to match selection; control closes and shows selected value2.1.1 Keyboard Keyboard
Inspect the mode switcher in DevToolsUses ListBox ARIA pattern (role="listbox", role="option", aria-selected); has a label identifying the control4.1.2 Name, Role, Value Visual Inspection
Navigate options with NVDA runningNVDA announces the control's label, current selection, and each option4.1.2 Name, Role, Value Screen Reader

SiteLogo

Interaction Expected Behavior WCAG Criterion Test Method
Tab to the site logo linkLogo link is focusable with visible focus ring2.4.7 Focus Visible Keyboard
Inspect the logo img or picture element in DevToolsalt attribute provides meaningful text identifying the site; no flash of missing alt text during theme transition1.1.1 Non-text Content Visual Inspection
Switch themes and observe the logo with prefers-reduced-motion: reduce enabledLogo transition animation is suppressed or instantaneous2.3.3 Animation from Interactions Visual Inspection
Focus the logo link with NVDA runningNVDA announces the link with meaningful alt text (not a filename)1.1.1 Non-text Content Screen Reader
REQ-00013 implemented Theme switching shall persist user preference across sessions.
REQ-00015 implemented A theme control component shall support Light, Dark, and System modes.
REQ-00016 implemented Persisted theme preference storage shall be implementation-agnostic and replaceable without affecting component contracts.
REQ-00051 implemented The SiteLogo component shall render an SVG logo (preferred), PNG logo (fallback), or site title text (final fallback) based on what is configured in src/config/site.ts, with no props required for default behavior.
REQ-00146 implemented Template V1 shall provide a configurable primary navigation system that supports nested menu nodes.
REQ-00147 implemented Navigation implementation shall separate menu data contract, resolver logic, and render/layout integration responsibilities.
REQ-00148 implemented Menu behavior bindings shall use stable `data-*` hooks rather than style-class selectors.
REQ-00149 implemented Primary navigation and submenu controls shall be keyboard-operable with deterministic focus management and correct ARIA state announcements.
REQ-00150 implemented Menu customization APIs shall preserve structural and behavior-critical classes/attributes, limiting overrides to documented slots or token-driven surfaces.
REQ-00156 implemented Site identity constants (title, home URL, logo paths, and logo alt text) shall be defined in a single canonical configuration file (src/config/site.ts) and imported by components; values shall not be hard-coded inline.
REQ-00157 implemented The SiteLogo component shall support a primary logo and an alternate logo variant; the alternate shall activate automatically based on the active color-scheme theme, and shall be overridable via an explicit prop that takes precedence over the theme-driven default.
REQ-00158 implemented The SiteLogo component shall use an explicit alt text value when configured; when not configured it shall fall back to the site title from src/config/site.ts.
REQ-00159 implemented The theme switcher shall be available in two variants: an icon-cycling button (ThemeIconSwitcher) and a text select dropdown (ThemeModeSwitcher). Both shall support Light, Dark, and System modes and share the same preference storage contract.
REQ-00160 implemented The active theme switcher variant shall be configurable via a single field in src/config/site.ts (themeSwitcher: 'icon' | 'select' | 'none') without requiring changes to header or layout components.
REQ-00161 implemented When themeSwitcher is set to 'none', no theme control shall be rendered and the site shall present in light mode only; no switcher markup shall appear in the desktop header or mobile dialog.
REQ-00162 implemented The icon theme switcher shall cycle through modes in the order system -> light -> dark -> system on each click, display the icon corresponding to the current preference, and update its aria-label and title to reflect the active mode.
REQ-00163 implemented The primary navigation shall include a search entry point (icon link) in both desktop and mobile contexts, implemented via Search.astro with a data-search-trigger attribute for future modal wiring.
REQ-00228 implemented The site header shall support configurable scroll behavior modes — static (default), CSS sticky, and scroll-up (hides on scroll down, reveals on scroll up) — selectable via site configuration (HeaderConfig).
REQ-00229 implemented The theme mode switcher shall support three presentation variants — icon toggle (cycling light/dark/system), select dropdown (explicit mode selection), and disabled (no UI) — selectable via site configuration.

Search

Search across pages and articles. Use arrow keys to navigate results.

Search across pages and articles.

Loading search...

Search is unavailable. Please try again later.

    No results for ""

    Try different keywords or fewer words.