ComponentsLayout & themingTopbar
On this page
Layout & theming

Topbar released

The bar across the top of an application: a start for the menu button or a brand, the screen's title, and an end for actions, the signed-in user and sign-out, on one row at every width.

  • Import@pacit/components/topbar
  • Selectorsheader[pctTopbar], [pctTopbarTitle]

No APG pattern applies, and none is invented — the reasoning is under Accessibility ↓

Living room

topbar.demo.ts — the same file runs here and is highlighted under Code

Start here

Usage

One import per component, and the shortest possible use — the package itself is installed once, on the start page.

Import this component
import { PctTopbar, PctTopbarTitle } from '@pacit/components/topbar';
ExportedWhat it is
PctTopbarTitlecomponentunreleased[pctTopbarTitle]The screen's title in the top bar: the consumer's heading, at the level the page's outline needs, in the bar's type, on one line that ends in an ellipsis when the bar runs out of room.
PctTopbarcomponentunreleasedheader[pctTopbar]An application's top bar: three slots on one row — the start (the button that opens the navigation on a narrow screen, a brand), the screen's title, and the end (actions, the signed-in user, sign-out).
<header pctTopbar>
  <h1 pctTopbarTitle>Living room</h1>
  <button pctButton pctTopbarEnd variant="outline">Sign out</button>
</header>
Examples

Every state, running

Each example is the component itself, not a screenshot. Open the code under any of them.

A frame the application arranges: the navigation at the side while the window is wide, in a

drawer opened from the bar while it is narrow. The width is the application's, read after the first render so the server's markup and the client's first one agree, and the drawer is shut before a widening takes its content away.

Rooms

Ada Lovelace

The Rooms screen.

API

What it reads, and what it writes back

Read from the source at build time — the types, the defaults and the lines of documentation are the library's own.

unreleased marks 2 of them: on main, and not in @pacit/components@0.2.0 — the next release names the version.

An application's top bar: three slots on one row — the start (the button that opens the navigation on a narrow screen, a brand), the screen's title, and the end (actions, the signed-in user, sign-out).

Inputs

NameTypeWhat it does
No inputs — the element is configured by its content.

Outputs

None of its own. The native events — (click), (focus), the form's submit — are the platform's, and two-way models above carry the rest.

What it writes on the element

The hooks a stylesheet or a test can hold. Stable by contract — the parts inventory gate fails the build when one disappears.

AttributeBound toNote
classpct-topbar
Styling

Parts to select, tokens to override

Two ways in, both stable. No class names to depend on.

Parts

In a test, PctTopbarHarness, PctTopbarTitleHarness from @pacit/components/testing read a part by the name below and a state by its attribute — the harness gate holds the names to the package.

PartSelectorWhat it is
end[data-pct-part="end"]the region after the title, at the bar's end
heading[data-pct-part="heading"]the region of the title, taking what is left
start[data-pct-part="start"]the region before the title

Tokens

Every value the component reads, with the light and the dark default. Override a token at any scope and it cascades like any custom property.

TokenStyles
--pct-topbar-bg{pct.surface}#ffffff · #0f172aBackground of the top bar.
--pct-topbar-border{pct.border}#e2e8f0 · #334155Colour of the edge under the top bar.
--pct-topbar-fg{pct.text}#0f172a · #ffffffText colour of the top bar.
--pct-topbar-gap{pct.space.3}8pxGap between the start, the title and the end, and between the controls of each.
--pct-topbar-height56pxMinimum height of the top bar.
--pct-topbar-padding-x{pct.space.5}16pxInline padding inside the top bar.
--pct-topbar-title-font-size{pct.font.size.lg}16pxFont size of the screen's title.
--pct-topbar-title-font-weight{pct.font.weight.medium}500Font weight of the screen's title.

Theming

A brand override in a few declarations. Scope it to a theme attribute, a page or one container — the component never notices.

[data-theme='brand'] {
  --pct-topbar-bg: #f0fdfa;
  --pct-topbar-fg: #134e4a;
  --pct-topbar-border: #99f6e4;
}

Living room

Accessibility

Measured, not assumed

Pattern: none — the bar is the consumer's <header>, which the platform makes the page's banner landmark when it stands outside an <article>, <aside>, <main>, <nav> or <section>. The bar writes no role. Named in the class JSDoc.

Keyboard

keyeffecttest

Empty by design: the bar handles no key. Tab walks its controls in the reading order of its slots — start, title, end — measured in apps/sandbox-e2e/src/frame.spec.ts, "Tab walks the bar in its reading order, then the navigation".

What is measured, and what is not yet

Every line is a test in three engines or a gate on every commit. The gaps are listed the same way, each with the requirement that will close it.

MeasuredARIA pattern named in the class JSDoclibs/components/topbar/src/topbar.ts
n/aKeyboard map tested key by keynot applicable — no handler; the reading order is measured in apps/sandbox-e2e/src/frame.spec.ts
Measuredaxe audit on the component's own sandbox viewapps/sandbox-e2e/src/a11y.spec.ts — /frame in SBX_ROUTES
MeasuredVisual screenshotapps/sandbox-e2e/src/visual.spec.ts — frame-app and frame-app-rtl
Measuredforced-colors: active — no state carried by colour alonelibs/components/topbar/src/topbar.scss — the edge CanvasText; the bar has no state. The reading: apps/sandbox-e2e/src/forced-colors.spec.ts
n/aprefers-reduced-motion — duration from a tokennot applicable — the bar moves nothing
n/aTouch target ≥ 24×24 px outrightnot applicable — the bar's targets are the consumer's controls
MeasuredSize axis aligned to --pct-control-height-*deliberately not: the bar is not a control; its height is a floor of its own (--pct-topbar-height), above the tallest control it holds
MeasuredDensity axislibs/tokens/src/component.topbar.json — the padding and the gap stand on space.3 and space.5, which the compact scope re-points
MeasuredRTL — no physical properties + a dir="rtl" screenshotlogical properties only, held by check-styles; the screenshot frame-app-rtl, where the end stands at the left
MeasuredSSR + hydration with no NG05xxapps/sandbox-e2e/src/hydration.spec.ts — /frame in SBX_ROUTES
n/aFormsnot applicable — the bar holds no value
MeasuredParts registered in the inventorylibs/components/parts.snapshot.md — the three parts above
MeasuredTokens registered + an entry in contrast.policy.jsonlibs/tokens/src/contrast.policy.json — the text on the bar (AA error), the edge (UI warn); libs/tokens/tokens.snapshot.md
n/aStrings through PCT_TEXTSnot applicable — the component writes no word of its own
MeasuredEntrypoint size budgetlibs/components/size.snapshot.md — the ./topbar row
MeasuredScreen-reader logRead 2026-11-02 from docs/acr/at/ — three readers, whole sandbox. The bar's controls are read as themselves, in the bar's order: Orca Sign out · button. and, on the long title's bar, Edit · button.; NVDA Edit, button; VoiceOver Sign out button, and lost the page after it. The bars of /frame stand inside the sandbox's <main>, so none of the three is a banner and no reader said one — the landmark is the platform's answer where the outline makes the <header> the page's own
MeasuredA docs page with live examplesapps/sandbox/src/app/views/frame/ (the sandbox view). The published site: /components/topbar, its demos' source in the code tab
MeasuredUnit + mutationlibs/components/topbar/src/topbar.spec.ts; the narrow Stryker run of 2026-11-02 generates zero mutants for topbar.ts — two classes with no logic offer nothing to break. The file stays in mutation.policy.json, so the day it grows logic the report owes a row
Evidence

What the repository holds for this component

Read at build time from the tracked snapshots, not typed here.

—mutation scoreno mutants to kill — the policy says why
—e2e casescovered by the shared suites only
2colour pairs measuredlight and dark, at every token build
0visual baselinesChromium, the sandbox's own pictures
7elements in the preview0 listeners · 1 render to settle · 1688 µs in jsdom, held exactly but for the clock

Known limitations

  • Not sticky. The bar scrolls with the page. position: sticky and inset-block-start: 0 on the consumer's <header> keep it up; the bar does not choose for every application.
  • One row only. There is no second row for tabs or a search field under the title; a row of the application's own under the bar is one.
  • The title is cut, not wrapped. A long title ends in an ellipsis; its full text is in the document for a reader and find-in-page, and for the eye only through a tooltip the application adds.
  • A banner only where the platform says. Inside a <main> or a <section> the <header> is no landmark, and the bar does not force one.