Navigation
The unified site header: brand, section nav, app switcher, and account menu — one component, ws_header().
data-property attribute and a .property-switcher-* / .ps-* class family. None of those exist in includes/components/header.php — the real API is ws_header($section, $opts) and the real prefix is .wsh-. The old per-app colour table also listed four retired pre-unification hexes. Everything below is checked against the current source.
When to Use
ws_header('planner') with the section the page belongs to — that one argument drives the brand wordmark, the nav items, the active state, and the app switcher's current entry.
ws_app_bar() beneath the header, not instead of it. Never hand-roll a header; the account and auth wiring lives here.
Variants
Sections
The section key selects a preset: its nav items, wordmark, and accent. Seventeen are defined; passing an unknown key falls back to home rather than erroring.
| Key | Surface |
|---|---|
home | Main site — the fallback for any unrecognised key |
library | Exercises, workshops, icebreakers, collections, goals |
planner | Workshop Builder |
facilitator | Facilitator app |
synthesize | Post-session synthesis |
coach | AI Coach |
strategist | Workshop Strategist |
sidekick | Sidekick |
agenda-check | Agenda Check |
intervention | Intervention Cards |
myworkshopr | MyWorkshopr dashboard |
dna | Facilitator DNA |
academy | Academy / training |
tips | Tips |
podcasts | Podcasts |
toolkits | Toolkits, including the Hybrid Kit |
roi | ROI calculator |
<?= ws_header('planner') ?>Custom nav items
Passing nav in the options replaces the section's preset nav entirely — useful for a sub-app that shares a section but needs its own links.
<?= ws_header('library', ['nav' => [
['label' => 'Exercises', 'href' => '/library/exercises/'],
['label' => 'Icebreakers', 'href' => '/library/icebreakers/'],
]]) ?>Resolution order
The section is resolved from the first of these that's set, so most pages never pass it explicitly — they set a global once and the header picks it up.
| Order | Source |
|---|---|
| 1 | The $section argument |
| 2 | $GLOBALS['header_section'] |
| 3 | $GLOBALS['header_property'] — the legacy name, still honoured |
| 4 | 'home' |
States
| State | Behavior |
|---|---|
| Active nav item | Marked with aria-current="page", so the current location is conveyed structurally rather than by styling alone. |
| App switcher closed | The trigger is labelled "Switch app" with aria-expanded="false" and aria-haspopup="dialog". |
| App switcher open | The panel lists apps grouped by phase, each with a name and description. |
| Signed out | A sign-in control replaces the account cluster. |
| Signed in | Avatar initials and name, with a menu trigger labelled "Account menu" carrying aria-haspopup="menu". |
| Styles emitted once | The component tracks a static flag and emits its CSS on first call only, so multiple invocations on one page don't duplicate it. |
Real-World Usage
The standard page opening. Most pages set the global in their config block and let the shared header include do the call.
<?php require_once 'config.php'; require_once 'includes/shared-auth.php'; $header_section = 'library'; // resolved by ws_header() require_once 'includes/header.php'; ?> <!-- …page content… --> <?= ws_footer() ?>
Calling it directly is equivalent, and clearer in a standalone template:
<?= ws_header('library') ?>Options
| Option | Type | Default | Purpose |
|---|---|---|---|
$section | ?string | null | Positional. One of the 17 keys above. null falls through the resolution order. |
nav | array | the section's preset | Replaces the nav items. Each entry takes label and href. |
The signature is ws_header(?string $section = null, array $opts = []). It returns markup as a string, like the other ws_* helpers.
Accessibility
Among the better-wired components in the platform — every interactive cluster carries a name and a state.
| Concern | Behavior |
|---|---|
| Landmark | The header is labelled "Site header", and the nav is labelled per section — so a page with several navs stays distinguishable. |
| Current page | aria-current="page" on the active nav item. |
| App switcher | Trigger labelled "Switch app", with aria-haspopup="dialog" and aria-expanded reflecting the panel. |
| Account menu | Labelled "Account menu" with aria-haspopup="menu". The avatar control is labelled "Account". |
| Icon controls | Search and MyWorkshopr shortcuts carry explicit aria-labels rather than relying on their glyphs. |
| Keyboard | All triggers are real buttons and nav items real links, so tabbing and activation are native. |
| Focus in panels | Worth checking per surface: the switcher and account panels don't trap focus or return it to the trigger on close — the same gap as Modal. For a dropdown this is less severe than for a dialog, but Esc-to-close with focus return would be an improvement. |
Tokens
The header emits its accent as an inline custom property computed from the section, with a literal fallback beside the var() so it still paints if tokens haven't loaded.
#F87171 to Tips, #BE123C to Podcasts, #0284C7 to Planner, and #7C4DFF to Coach and Interventions. All were retired by Red Unification — --phase-learn, --phase-plan, --phase-facilitate, and --phase-reflect all resolve to #E54D4D. Sections still exist as semantic grouping; they no longer differ in colour. See Colors.
Beyond the accent, the header draws on the shared surface, ink, radius, and type scales. Read the .wsh-header rules in includes/components/header.php — the styles are emitted by the component itself rather than living in components.css.
CSS Classes
The prefix is .wsh-. The .property-switcher-* and .ps-* classes this page used to list do not exist.
| Class | Purpose |
|---|---|
.wsh-header | The <header> landmark |
.wsh-header__left / __right | The two clusters |
.wsh-header__brand / __brandwrap / __mark / __word / __tag | Logo, wordmark, and section tag |
.wsh-header__nav / __navitem | Section nav and its links |
.wsh-header__switcher / __panel | App switcher trigger and panel |
.wsh-panel__group / __app / __desc | Phase group, app entry, and its description inside the panel |
.wsh-header__account / __avatar / __avatar-initial | Account cluster |
.wsh-header__menu / __menu-head / __menu-name / __menu-email / __menu-item | Account dropdown |
.wsh-header__submenu / __submenu-item | Nested menu level |
.wsh-header__signin | Signed-out control |
.wsh-header__icon-btn | Search and shortcut buttons |
.wsh-header__caret / __dot | Disclosure caret and status dot |
Files
| File | Purpose |
|---|---|
includes/components/header.php | ws_header() — the section table, markup, and inline styles. The source of truth for this page. |
includes/header.php | Shared page include that calls ws_header() |
design-system/platform-tokens.css | Phase tokens the accent resolves against |
PRODUCT.md §5 | Canonical app inventory — the header's section list should match it |