[C09] Page Header (component)
Summary
C09 is a component spec, not a screen — the second such entry after C07 Data Table. It is the canonical page header for the admin portal: the band that sits directly beneath the shell chrome and names the screen the operator is looking at.
Every admin-portal screen has a page header. As of 2026-08-26 no two agree. An audit of the fourteen routed screens found six distinct header idioms across three heading levels, and nine screens leaking a use-case ID (T03 · Stock return) into production markup. This page is the contract that ends that drift; the Angular component ap-page-header is its single implementation.
|
The page header does not own screen identity at instance scope. C01's entity header — the band the shell renders above the tab strip whenever the URL carries an instance — already names the event, its status and its dates, and already carries the document’s |
|
Design landed 2026-08-26. |
Actor & Context
Actor: indirect — operators meet C09 on every screen; the component has no actor of its own.
Frequency: every page render.
Precondition: the consuming screen knows its own section and title. Nothing else.
Entry point: mounted by the screen as <ap-page-header>, immediately inside the router outlet, above all other content.
Anatomy
┌──────────────────────────────────────────────────────────────┐
│ ENTRIES ← eyebrow (section) │
│ Participants ← title (the screen) │
│ 1 240 rows across 4 categories ← subtitle (optional) │
│ [Import] [Add] [Export] ← [actions] │
└──────────────────────────────────────────────────────────────┘
| Slot | Required | Content |
|---|---|---|
|
Yes |
The section the screen belongs to, in the vocabulary the sidebar and tab strip already use — |
|
Yes |
The screen, as a noun phrase: |
|
No |
One line of orienting context — a count, a scope note, a state summary. Muted, small. Omitted rather than filled with filler. |
|
No |
Projected content, right-aligned, vertically bottom-aligned with the title. The screen’s primary verbs, or the bulk-action bar that replaces them. Owned by List Screen Affordances; C09 only provides the slot and guarantees its position is height-stable. |
|
No |
Projected content beneath the subtitle — stage bars, progress pills, chips. The import flow’s four-stage bar lands here. |
Scope rule — who owns the <h1>
The portal has two headers stacked, and only one of them may be the document heading.
| Scope | Entity header (C01) | Page header (C09) |
|---|---|---|
Tenant — |
Not rendered. |
Carries the |
Instance — |
Rendered. Carries the |
Demotes to |
The component derives its own heading level from ScopeService; consumers never choose it. This is not a styling preference — it is the accessibility contract. Exactly one <h1> per page, and it is the most specific thing on screen that the operator navigated to.
-
Participants — Event 145beneath a header that already says Killarney 1. The suffix is noise; the id is not operator vocabulary. -
A second
<h1>restating the event name, with a second status chip that can disagree with the first (the E01 defect logged as T1.6 in the hardening journal). -
Screens with no heading element at all, where the visually-largest text is a
<div>.
Eyebrow vocabulary
The eyebrow answers "where am I?", so it must speak the same language as the thing the operator clicked to get here.
|
The eyebrow is derived, not authored. The design side settled this on 2026-08-26 with a The Angular implementation must do the same. |
| Rule | Detail |
|---|---|
Use the section |
The sidebar group at tenant scope, or the tab at instance scope. The eyebrow and the highlighted nav item always match — because both read the same registry entry. |
Never a use-case ID |
|
Never a restatement of the title |
|
Sentence case, rendered uppercase |
Authored as |
Use-case IDs are not shipped
Nine screens shipped their use-case ID in the eyebrow. The provenance is traceable: the Claude Design canvases label each artboard with its ID — necessary there, because the canvas is a wall of twenty screens and the label is how you find one — and the implementation transcribed the artboard label as page content.
Decision (2026-08-26): the IDs are removed from the UI entirely. They are not rendered in development builds either. The affordance for cross-referencing a screen to its spec is the file, not the running app.
The rule is enforced, not merely documented, in two places. PageHeader strips a leading `<prefix><NN> · ` from any eyebrow handed to it, so a transcribed code cannot render. A unit test additionally scans component templates for the pattern and fails the build.
Anchor the test’s pattern at the start of the string. An unanchored [CETMSA]\d{0,2} · ` matches ordinary content — a timestamp ending `SAST · ` matches on the `T — and a guard that cries wolf gets deleted.
| Screen | Was | Now |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Membership import |
|
derived when the screen is designed |
Three of these differ from the values first guessed in this spec (Inventory for T02/T03, Money for C06). That is the point of deriving rather than authoring: the guesses were plausible and wrong, because Inventory is not a sidebar group and orders sits in the Events group. The derivation is authoritative; where it reads oddly, the fix is the nav model, not the eyebrow.
T05 is the clearest case. Its old eyebrow named a section — Inventory — that does not exist in the sidebar at all, and the screen had already been moved to the Insights anchor in the 2026-08-14 rewire. Nothing flagged the mismatch because the string was typed by hand.
|
Open — C06 under |
The C05 stage screens dropped Step N of 4 along with the code. The stage bar immediately below the header already carries the step, and an eyebrow is a section, not a progress indicator.
The E08 pair is worth noting on its own: the upload step says Import results and the summary step says Result import. Two labels, one flow, differing by word order — the kind of drift that only a shared component with a fixed vocabulary prevents.
API Surface
| Input | Type | Purpose |
|---|---|---|
|
|
Section label. Required. |
|
|
Screen name. Required. |
|
|
Optional orienting line. Omit rather than pass |
| Content slot | Purpose |
|---|---|
|
Right-aligned verb group or bulk bar. |
|
Stage bar, progress chips, secondary context beneath the subtitle. |
Heading level is derived, not an input. There is deliberately no headingLevel prop: making it a choice makes it a per-screen decision, which is the problem this spec exists to remove.
Visual Treatment
Locked to the values import-page-shell already ships, which are the most-reviewed of the six current idioms:
| Element | Treatment |
|---|---|
Band |
White, |
Eyebrow |
|
Title |
|
Subtitle |
|
Actions |
Bottom-aligned with the title baseline; flex, |
The <h2> at instance scope renders at the same size as the <h1> at tenant scope. Demotion is semantic, not visual — the operator should see one consistent header treatment across the portal.
Consumers
All fourteen routed screens, plus the four nested detail routes. import-page-shell retains the two-column body and right rail, and mounts ap-page-header for its band rather than drawing its own.
Out of Scope (today)
-
Breadcrumbs — owned by C01’s topbar.
-
Entity identity, status chips, tab strips — owned by C01’s entity header.
-
Action-button semantics, stats strips, filter chips — owned by List Screen Affordances.
-
Responsive collapse below 720px. The portal is desktop-first; the mobile treatment needs its own pass.
Design Decisions
| Decision | Rationale |
|---|---|
Use-case IDs removed from the UI entirely, including dev builds |
They are internal filing references with no operator meaning and no in-app lookup. A dev-only variant was considered and rejected: a conditional label is one |
Heading level derived from scope, not passed in |
A |
Component lives in |
The existing header band is already correct; it is only unreachable. Promoting it is a move, not a rewrite. |
Eyebrow tracks the nav, not the module |
An operator’s mental model of "where am I" is built from what they clicked. Matching the eyebrow to the highlighted sidebar group or tab makes the header confirm the navigation rather than introduce a second taxonomy. |
A test guards the ID pattern |
Nine screens acquired the ID by transcription from a canvas, not by decision. Documentation alone would not have stopped any of them. |
Future Use Cases
FU-1 — Responsive collapse
Below the 720px breakpoint the eyebrow and subtitle likely fold into the title and the actions collapse to an overflow menu. Needs a design pass alongside the rest of the portal’s mobile treatment.
FU-2 — ALL TENANTS pill
C01 places the platform-scope ALL TENANTS pill "beside the page title". When platform screens are built, C09 gains a slot for it rather than each platform screen positioning its own.
FU-3 — Sticky behaviour
C06 currently sticks its header on scroll; no other screen does. Either every page header sticks or none does — pick one when there is a second screen that wants it.
Notes
|
Claude Design. The canvas project’s shared library ( |