Header

The bar across the top of the app. It holds global tools like search and the user's avatar, and stays the same as you move between pages.

Spec · from metadataOpen in Figma ↗

When to use

  • Top-level app header bar across all TimelyCare platforms
  • Need global search + actions + avatar in the top chrome
  • Responsive header that adapts desktop (3-column) to mobile (hamburger + stacked)
  • Composing with Sidebar for full app shell

When not to use

  • Marketing/landing page hero sections — use a custom layout instead
  • Page-level section headers — use a semantic <header> with heading elements
  • Navigation-only bar — use NavigationMenu directly

Anti-patterns

Avoid<Header>
  <a href="/home">Home</a>
  <a href="/about">About</a>
</Header>
Prefer<Header>
  <HeaderRight>
    <Input placeholder="Search..." />
    <Avatar>...</Avatar>
  </HeaderRight>
</Header>

Header is a structural shell for global chrome, not a navigation component. Breadcrumbs and page titles belong in the PageHeader compound (inside <main>); section nav goes in NavigationMenu or Sidebar.

Avoid<Header>
  <div className="flex justify-between w-full">...</div>
</Header>
Prefer<Header>
  <HeaderLeft>...</HeaderLeft>
  <HeaderCenter>...</HeaderCenter>
  <HeaderRight>...</HeaderRight>
</Header>

Use the provided slot sub-components instead of custom flex layouts. They handle responsive behavior and consistent spacing.

Avoid<Header className="h-20 bg-[#19518B]">
Prefer<Header>

Don't override the header height (h-16) or use arbitrary colors. The header uses bg-sidebar and border-b from the token system (matching the paired Sidebar background).

Accessibility

  • Required ARIAaria-label on HeaderMenuButton (auto-provided)
  • Min touch target48px
  • Screen readerHeader uses semantic <header> element. Mobile menu button announces expanded/collapsed state via aria-expanded.

Token bindings

TokenCategoryUsage
sidebarcolorHeader background (bg-sidebar, matches the paired Sidebar); text inherits foreground
bordercolorBottom border
4 (16px) / 6 (24px)spacingHorizontal padding (mobile / desktop)
2 (8px)spacingGap between header items

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

ElementValue
Header height64px (h-16), all viewports
Horizontal padding16px mobile (px-4) / 24px desktop (md:px-6)
Slot gap8px (gap-2)
Avatar size40×40px (size-10), rounded-xl
Search input width300px (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): HeaderCenter is visible; HeaderMenuButton is hidden.
  • Mobile (< 768px): HeaderCenter is hidden; HeaderMenuButton (hamburger) appears in HeaderLeft; HeaderMobileContent renders 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. In AppShell the top bar sits outside the <main> landmark, so it is an implicit banner and needs no explicit role. Only add role="banner" if you place a Header inside <main>, <article>, <aside>, <nav>, or <section>, where the implicit banner role does not apply.
  • HeaderMenuButton auto-provides aria-label ("Open menu"/"Close menu") and aria-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

ProblemSolution
Breadcrumb/title in the HeaderMove it to PageHeader inside <main> (ownership boundary).
Header border misaligned with SidebarKeep border-b on the same h-16 element; don't wrap in an extra bordered container.
Header collapses under tall contentKeep shrink-0 (the shell sets it) when overriding className.
Hamburger won't show on desktopExpected — HeaderMenuButton renders only < 768px.
Search visible on mobileHeaderCenter 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