Breadcrumbs and page titles live in PageHeader, inside
<main>— never in the Header. This is the "AppShell ≠ PageHeader" ownership boundary. The Header carries global chrome only.
Import
import {
Header, HeaderLeft, HeaderCenter, HeaderRight,
HeaderTitle, HeaderMenuButton, HeaderMobileContent,
} from "@timelycare/helix-ui"
Composition
The Header is a shell with three slots plus a mobile menu button. Fill the slots; the shell handles height, surface, padding, sticky behavior, and responsive rules.
<Header>
<HeaderLeft>
<HeaderMenuButton /> {/* mobile hamburger; renders only < 768px */}
</HeaderLeft>
<HeaderRight> {/* ml-auto — right-aligned cluster */}
<Input placeholder="Search…" className="w-[300px]" />
<Button variant="outline">Button</Button>
<Button>Button</Button>
<Avatar className="size-10 rounded-xl">
<AvatarImage src="/user.jpg" alt="User" />
<AvatarFallback className="font-normal font-sans">CN</AvatarFallback>
</Avatar>
</HeaderRight>
</Header>
HeaderCenter (centered, hidden on mobile) is optional — AppShell uses it for a
centered global search. In the standard right-aligned layout above it is unused.
Dimensions
| Element | Value |
|---|---|
| Header height | 64px (h-16), all viewports |
| Horizontal padding | 16px mobile (px-4) / 24px desktop (md:px-6) |
| Slot gap | 8px (gap-2) |
| Avatar size | 40×40px (size-10), rounded-xl |
| Search input width | 300px (hidden on mobile; use an icon trigger instead) |
The Header height matches the Sidebar header (h-16) so their bottom borders align
at the seam. Both use border-b; the Header background is bg-sidebar to match the
paired Sidebar surface.
Responsive behavior
- Desktop (≥ 768px):
HeaderCenteris visible;HeaderMenuButtonis hidden. - Mobile (< 768px):
HeaderCenteris hidden;HeaderMenuButton(hamburger) appears inHeaderLeft;HeaderMobileContentrenders below the bar when the menu is open. Responsive state is driven by viewport width (useIsMobile, 768px breakpoint), not container width.
HeaderMenuButton toggles the Header's own mobile-menu state, or — inside a
SidebarProvider — pass onClick={() => toggleSidebar()} to open the Sidebar's
mobile Sheet (the full navigation drawer). The nav drawer itself is a
Sidebar surface, not part of Header.
Accessibility
- Uses the semantic
<header>element. InAppShellthe top bar sits outside the<main>landmark, so it is an implicit banner and needs no explicitrole. Only addrole="banner"if you place a Header inside<main>,<article>,<aside>,<nav>, or<section>, where the implicit banner role does not apply. HeaderMenuButtonauto-providesaria-label("Open menu"/"Close menu") andaria-expanded.- Keyboard: Escape closes the mobile menu; Tab moves through header actions.
- Icon-only buttons in the header (search, actions) MUST have an
aria-label.
Gotchas
| Problem | Solution |
|---|---|
| Breadcrumb/title in the Header | Move it to PageHeader inside <main> (ownership boundary). |
| Header border misaligned with Sidebar | Keep border-b on the same h-16 element; don't wrap in an extra bordered container. |
| Header collapses under tall content | Keep shrink-0 (the shell sets it) when overriding className. |
| Hamburger won't show on desktop | Expected — HeaderMenuButton renders only < 768px. |
| Search visible on mobile | HeaderCenter is hidden < 768px by design; provide an icon-only search trigger in HeaderLeft/HeaderRight. |
See also
- PageHeader — breadcrumb + page title + description + page actions (inside
<main>) - Sidebar — left-rail nav + mobile Sheet the hamburger opens
- Breadcrumb — the breadcrumb primitive (used inside PageHeader)
- App Shell — composes Header + Sidebar + content