Components · Jaringan block
Header
The PPI mark and wordmark, the cabinet slot, five links and the account. Sticky and solid. The current page gets a small red station dot, the only red in the header.
Header
Built on the Navigation Menu (shadcn, on Base UI): the links are capsules, and this preview shows “Events” as the current page, marked with a small dot in the app's line. Narrow your window below 64rem to get the Menu button.

PPIT ShenzhenKabinet 2026/2027Show codeHide code
<SiteHeader
brand={<OrgBrand />}
cabinet={<CabinetSlot />}
links={[{ href: "/events", label: "Events" }, …]}
currentHref={usePathname()}
account={<SignInButton />}
menuExtras={<ThemeControl />}
linkAs={NextLink}
/>Line stripe
Along the top of every page runs the app's line, like the stripe on a train car: Danus orange in the shop, brand red elsewhere. While the next page loads, a station travels the stripe, the header's loading indicator; screen readers hear “Loading the page”. It starts on a click to another path of the site and stops when the path changes (useNavigationPending), so it needs no router.
Transfer
The other PPITSZ apps, as an interchange lists its lines: a Navigation Menu trigger with the interchange mark (its label shows from 80rem) opens them, each with one line on what is there. On phones they sit in the menu under the links, and the footer repeats them. The URLs come from the server (transferApps() in @ppitsz/accounts/urls), since they are env. Only the apps have it; a header with many links, like the organization site's, leaves it out.
Show codeHide code
<SiteHeader
brand={<ShopBrand />}
links={[{ href: "/", label: "Campaigns" }, { href: "/orders", label: "Your orders" }]}
currentHref={usePathname()}
line="danus" // the app's line: stripe and current-page dot
transfers={transferApps("shop")} // the other PPITSZ apps, from the server
account={<SignInButton />}
linkAs={NextLink}
/>Mobile menu
The logo plus a “Menu” button that opens a full-screen dialog. Inside, the links are stations on a vertical route in the app's line, with the current page as “here”; then Transfer, and the account and theme control below. Focus is trapped and returns to the Menu button.
Language switcher
An optional languageSwitcher slot. The apps are English-only and leave it empty (ADR 0007); it stays for a site that publishes the same pages in several languages. Each link carries lang, hrefLang and the full language name.
Show codeHide code
<SiteHeader … languageSwitcher={<LanguageSwitcher options={…} current="en" />} />Rules
<nav aria-label="Main">with a list, and a skip link “Skip to content” as the first focusable element.- Never transparent over the hero, never hidden on scroll, never color-changing.
- No hover-only dropdowns. “Events” links straight to the events index.
- One line per app, the same on every page of it. The stripe is the header's only full-width color.
- The train moves only while a page is on its way; under reduced motion it stands still.
- Never show a disabled nav item. The account slot reserves its size while auth loads, so the header never shifts.
Don’t
A transparent header over a darkened hero photo that turns solid on scroll.
Do
A solid bg header with a hairline border, the same on every page.