Top Header Desktop
Desktop navigation bar with brand, nav, search, actions and subRow slots. Six variants, five sizes, radius cascade, true-centre nav, scroll elevation, floating inset mode, skip link, and full RTL support.
pnpm add @tessinaui/uiWhen to use
- A persistent bar at the top of every page that identifies the product and holds its global navigation and utilities. Keep the same position and content from page to page so people learn where things are.
- Global utilities only: search, notifications, an account menu, one emphasized action. Anything specific to the current page belongs in the page.
- A short primary nav. A handful of destinations fit in the
navslot; a second tier goes insubRow(tabs, a category strip). - Product shells and marketing sites alike. Product bars are solid (
filledorelevated);transparentandblurare for marketing pages laid over a hero or scrolling content. - Keyboard users on content-heavy pages. Pair the bar with
TopHeaderDesktopSkipLinkso the first Tab stop jumps past it.
When not to use
- On a phone or in a collapsed layout. Use Top Header Mobile or a Sidebar for hamburger and drawer navigation.
- For menus with panels. The nav slot holds links; hover-intent and
aria-expandedmenus come from Navigation Menu composed into it. - For an announcement strip. Stack a Banner above the bar; it is never a header variant.
- For a page's own sections or views. Use Tabs.
- For a trail of parents. Use Breadcrumb, alone or as the bar's leading content.
- To hold many actions. Keep one emphasized action and make the rest ghost or outline; do not add nav links that read as verbs.
Installation
pnpm add @tessinaui/uiUsage
import {
TopHeaderDesktop,
TopHeaderDesktopBrand,
TopHeaderDesktopNavList,
TopHeaderDesktopNavItem,
TopHeaderDesktopActions,
} from "@tessinaui/ui";
import { Search } from "@tessinaui/ui"; // library Search in the search slot
import { IconButton } from "@tessinaui/ui"; // library IconButton in actions<TopHeaderDesktop
bordered
brand={
<TopHeaderDesktopBrand href="/" name="Tessina">
<img src="/logo.svg" alt="" className="size-7" />
</TopHeaderDesktopBrand>
}
nav={
<TopHeaderDesktopNavList>
<TopHeaderDesktopNavItem active href="/">Home</TopHeaderDesktopNavItem>
<TopHeaderDesktopNavItem href="/about">About</TopHeaderDesktopNavItem>
</TopHeaderDesktopNavList>
}
search={<Search variant="filled" rounded="full" placeholder="Search…" />}
actions={
<TopHeaderDesktopActions>
<IconButton variant="ghost" size="sm" aria-label="Notifications">
<Bell className="size-4.5" />
</IconButton>
</TopHeaderDesktopActions>
}
/>The bar publishes its tone through <Surface>, so library components dropped
into any slot — Button, IconButton, Avatar, Badge, Search — adapt to
primary / on-color / hero-overlay bars automatically. On those bars the
current nav item is the bar's inverse chip.
Examples
Default
A basic desktop header with brand, nav, and trailing actions.
App shell
The product bar: workspace switcher in the brand slot, global search with a
⌘K hint, create action, notification badge, and an avatar account menu —
composed entirely from library components.
Commerce
Double-row store header: search-dominant main row, wishlist, cart count,
locale selector, and a category nav in the subRow slot with the underline
indicator.
Marketing
An announcement Banner stacked above the bar (a sibling — never a header
variant), a "New" badge on a nav item, a disabled item, and the quiet-login +
filled-signup pair.
Sub row
The subRow slot stacks any second row under the bar — here a Tabs strip.
It inherits the bar's horizontal padding and maxWidth containment.
Breadcrumb bar
The doc-tool pattern: a Breadcrumb trail as the
bar's leading content.
Transparent over a hero
variant="transparent" inside <Surface ink="light"> — the bar inherits the
plate's ink with no tone prop at all.
Blur + scroll elevation
variant="blur" + sticky + scrollBehavior="elevate": flat at rest, then a
hairline and shadow the moment content scrolls underneath. Inside a coloured
<Surface> the glass stands down and the bar paints opaque, as filled: over
a flat plate the glass is mostly the plate, and a focus ring on the bar would
lose its ground.
Floating (inset)
inset detaches the bar from the screen edges; its shell corners join the
rounded cascade (full ⇒ rounded-3xl).
Active-item styles
indicator="pill" (default) vs indicator="underline", plus a disabled item
that drops its href and leaves the tab order.
Skip link
TopHeaderDesktopSkipLink — invisible until keyboard focus, the first Tab
stop, jumping straight to your content region (WCAG 2.4.1).
Dropdown menu
A nav item that opens a hover/focus dropdown — composed from NavigationMenu,
which owns the aria-expanded / keyboard contract.
Mega menu
Full-width mega menu with a two-column product grid and a promotional footer card.
Enterprise dashboard
Icon-labelled nav items, ghost icon actions, and a primary CTA.
Variants
The six visual styles.
Nav positions
start, center (truly centred — a 1fr auto 1fr grid, so the centre cell
holds position regardless of side widths), and end.
With search
The search slot fills the flex-1 middle zone. The zone is a plain box — the
field you compose owns the search landmark, so
Search brings its own and nothing is nested.
Passing a bare <input>? Wrap it yourself in <search> or
<form role="search">.
Sizes
Five height and padding scales — the bar's controls and search step with them.
On color
A transparent bar inherits the tone around it, so over a coloured Surface or a hero its brand, nav and actions take the plate's ink. primary and on-color publish on-color themselves, and blur turns opaque on a coloured plate so its focus rings keep their ground.
Loading
The built-in skeleton mirrors the header layout — including navPosition —
to avoid content shift.
RTL
Set dir="rtl" and the bar mirrors: the brand sits at the right, the nav runs
right to left, and the actions sit at the left. Logos and icons do not flip.
An explicit dir="rtl" also defaults the header's own strings to Arabic: the
nav landmark's name and the skip link's text. Leave dir unset and the header
follows the page, in English. label and the skip link's children still
override their defaults, and strings overrides any entry of
TOP_HEADER_DESKTOP_STRINGS.
API Reference
TopHeaderDesktop
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "filled" | "elevated" | "transparent" | "primary" | "on-color" | "blur" | "filled" | Visual style |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Bar height scale — see the height table below |
rounded | "none" | "sm" | "md" | "lg" | "full" | "full" | Corner cascade for everything inside the bar (nav pills, search, icon buttons, skeleton bars) and, when inset, the shell itself. An attached bar's own corners stay square |
brand | ReactNode | — | Leading slot: logo, workspace switcher, breadcrumb |
nav | ReactNode | — | Navigation slot — position via navPosition |
navPosition | "start" | "center" | "end" | "start" | center is truly centred via a 1fr auto 1fr grid |
search | ReactNode | — | Search slot; a plain box, the composed field owns the search landmark |
actions | ReactNode | — | Trailing actions |
subRow | ReactNode | — | Second full-width row under the bar (tabs, category nav, breadcrumb strip) |
bordered | boolean | false | 1px bottom hairline |
sticky | boolean | false | sticky top-0 z-40 (top-3 when inset) |
inset | boolean | false | Floating bar: margins, hairline, shadow, shell corners from rounded |
scrollBehavior | "none" | "elevate" | "none" | elevate: data-scrolled + hairline + shadow once content scrolls under (IntersectionObserver sentinel; works in any scroll container) |
indicator | "pill" | "underline" | "pill" | Active nav-item treatment |
maxWidth | "sm" | "md" | "lg" | "xl" | "2xl" | "full" | "full" | Content-row containment — 48 / 64 / 72 / 80 / 96rem |
label | string | strings.nav | Accessible name of the nav landmark — make it unique per page, never containing the word "navigation" |
skeleton | boolean | false | Loading placeholder |
dir | "ltr" | "rtl" | inherited | Direction; "rtl" also gives the header and the parts inside it the Arabic strings |
strings | Partial<TopHeaderDesktopStrings> | — | Overrides single entries — the nav landmark's name, the skip link's text. label wins for the nav name |
tone | "default" | "on-color" | auto | Published via <Surface>; auto-on-color for primary/on-color variants, otherwise inherited from the surrounding Surface |
Bar heights: xs 48px · sm 56px · md 64px · lg 72px · xl 80px
(min-heights on the content row; xs/sm match the common 48px app shell and
56px global nav).
TopHeaderDesktopSkipLink
First-Tab "Skip to content" link (WCAG 2.4.1). Render it first inside brand,
or immediately after <body>.
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | "#content" | Jump target — your main content region |
children | ReactNode | strings.skipLink | Link text; inside the header it follows the header's language |
dir | "ltr" | "rtl" | — | "rtl" gives the link the Arabic text on its own, for a link rendered outside the header |
strings | Partial<TopHeaderDesktopStrings> | — | Overrides skipLink; children wins |
TopHeaderDesktopBrand
<a> (or anything via render) with focus ring and flex gap.
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | — | Brand name next to children |
href | string | — | Link destination |
render | ReactElement | fn | — | Replace the element — render={<Link href="/" />} |
TopHeaderDesktopNavList
<ul> container for nav items and dividers.
TopHeaderDesktopNavItem
<a> (or anything via render) styled as a nav link.
| Prop | Type | Default | Description |
|---|---|---|---|
active | boolean | false | Current page — aria-current="page" + the bar's indicator treatment |
disabled | boolean | false | aria-disabled, href dropped (leaves tab order), muted paint |
hasDropdown | boolean | false | Chevron only — compose NavigationMenu for a real menu |
render | ReactElement | fn | — | Replace the element — render={<Link href="/pricing" />} |
TopHeaderDesktopNavDivider
<li> vertical separator between nav items.
TopHeaderDesktopActions
Flex row wrapper for trailing action elements.
Deprecated sub-components
TopHeaderDesktopSearch, TopHeaderDesktopIconButton and
TopHeaderDesktopAvatar are deprecated (kept working for one minor).
Slot the library components instead — they carry the full contracts and adapt
to the header's Surface tone automatically:
| Deprecated | Use instead |
|---|---|
TopHeaderDesktopSearch | Search (variant="filled" rounded="full") |
TopHeaderDesktopIconButton | IconButton (variant="ghost") |
TopHeaderDesktopAvatar | Avatar inside a button / DropdownMenu trigger |
Variants
- Filled (default) — solid
bg-backgroundapp bar. - Elevated — filled +
shadow-sm, for bars floating over scrollable content. - Transparent — no fill; overlay a hero (
className="absolute inset-x-0 top-0") inside a<Surface ink>so the tone is inherited. - Primary — brand-coloured. Rare in the wild (3 of 206 surveyed headers) — prefer
filledoron-colorunless the brand calls for it. - On Color — dark plate with light ink; the common "dark bar on a light app" (24 of 206 surveyed).
- Blur — frosted glass; pair with
stickyandscrollBehavior="elevate". Opaque inside a coloured<Surface>.
Transparent and blur are marketing-page patterns — the survey found zero in product shells. Product bars are solid.
Boundaries
- Announcement strips stack a
Bannerabove the bar — never a header variant. - Real nav menus (
aria-expanded, hover intent, keyboard) come fromNavigationMenucomposed into thenavslot. - Collapsed/hamburger navigation belongs to
TopHeaderMobileandSidebar. - Crowded navs: keep ≤5 primary items. A nav that must hold more takes NavigationMenu's own bar, whose overflow moves items into a "More" menu one at a time.
- Keep one emphasized action in the bar — everything else ghost/outline.
Keyboard Navigation
| Key | Action |
|---|---|
Tab / Shift+Tab | Move between the bar's interactive elements |
Enter / Space | Activate focused link or button |
Enter on skip link | Jump to the content region |
Menus composed via NavigationMenu add the full APG pattern: arrows rove
between triggers, ArrowDown enters an open panel, Home/End jump,
Esc closes and returns focus to the trigger.
Accessibility
- Renders
<header>without a hardcodedrole— it is abannerlandmark exactly when your page places it at body level (APG rule). <nav aria-label={label}>wraps the nav slot; setlabeluniquely per page and never include the word "navigation".- The search slot is a plain box — no landmark of its own, so a composed
Search(which rendersrole="search") is not nested inside a second one. Wrap a bare<input>yourself if you want the landmark. - Active item:
aria-current="page". Disabled item:aria-disabled, nohref, out of the tab order. TopHeaderDesktopSkipLinkprovides the WCAG 2.4.1 bypass block.- Touch targets floor at 44px wherever the pointer is coarse — phones, and tablets at any width. A search input in the bar floors at 16px text there too, so iOS does not zoom the page on focus.
- Focus rings visible on every interactive part, both themes.
dir="rtl"flips the layout via logical properties.- AT matrix: VoiceOver + Safari passed 2026-09-14. Reading from the top: "link Tessina", "Main navigation", "list 3 items", "current page link Overview", "link Analytics", "link Reports", "Search button", "Account button". NVDA and JAWS untested (no Windows host).
Migration (from the pre-TES-35 API)
- Attached-bar corner rounding is gone:
roundedno longer curves the bar's own bottom edge (it drives the inner cascade, and the shell only wheninset). Want the old look? AddclassName="rounded-b-md"etc. maxWidthremap:lgis now 72rem (was 80),xlis 80rem (was a duplicate 1280px).2xl/sm/md/fullunchanged.rounded="xl"never existed — the old docs table was wrong; the scale isnone–full.TopHeaderDesktopToneis now an alias ofSurfaceTone.- The three deprecated sub-components keep rendering unchanged for one minor.