AppShell

Top-level application chrome. Composes top bar (global chrome only) + sidebar + main content area. Universal across all 6 TimelyCare products — no variants. The AppShell-vs-PageHeader ownership rule is enforced via the typed AppShellTopBar sub-component, which has slots only for global chrome (logo, search, notifications, user menu, optional global actions) and NO slots for page chrome (title, breadcrumb, page actions — those live in PageHeader inside children).

Spec · from metadata

When to use

  • Every authenticated page in every TimelyCare product
  • The outermost layout — wraps the entire route content
  • When you need a left-rail nav + top bar + main content area

When not to use

  • Public/marketing pages with no authenticated chrome — use a custom layout
  • Email/print templates
  • Nested as a child of another AppShell — only one per route

Anti-patterns

Avoid<AppShell
  topBar={
    <header>
      <h1>Members</h1>
      <BreadcrumbBar items={crumbs} />
      <Button>Add member</Button>
    </header>
  }
  ...
>
Prefer<AppShell
  topBar={<AppShellTopBar logo={<Logo />} userMenu={<UserMenu />} />}
  ...
>
  <PageHeader>
    <PageHeaderBreadcrumb><Breadcrumb>...</Breadcrumb></PageHeaderBreadcrumb>
    <PageHeaderContent>
      <PageHeaderHeading>Members</PageHeaderHeading>
      <PageHeaderActions><Button>Add member</Button></PageHeaderActions>
    </PageHeaderContent>
  </PageHeader>
  <DataTable ... />
</AppShell>

AppShell top bar carries global chrome only; page titles, breadcrumbs, and page-specific actions live in PageHeader inside children. Using a raw <header> bypasses the typed AppShellTopBar that enforces this rule.

Avoid<AppShellTopBar
  logo={<Logo />}
  globalActions={<Button>Save changes</Button>}
  userMenu={<UserMenu />}
/>
Prefer<AppShellTopBar logo={<Logo />} userMenu={<UserMenu />} />
// ...elsewhere, inside the page:
<PageHeader>
  <PageHeaderContent>
    <PageHeaderHeading>Settings</PageHeaderHeading>
    <PageHeaderActions><Button>Save changes</Button></PageHeaderActions>
  </PageHeaderContent>
</PageHeader>

globalActions in AppShellTopBar is for actions always relevant app-wide (e.g. start a new visit from anywhere). Page-specific actions like Save Changes belong in PageHeader.actions.

Accessibility

  • Min touch target48px
  • Screen readerThe app-shell-main slot renders the document's single <main> landmark — SidebarInset around it is a plain <div>, so no main is nested inside another. AppShellTopBar renders a <header> that sits outside main and is therefore an implicit banner landmark (no explicit role needed). The PageHeader inside main is the route's h1. Sidebar inherits accessibility from the Sidebar primitive; nav-group expanders expose aria-expanded.
  • ContrastInherits color/contrast from background, foreground, and sidebar tokens. Active sidebar item uses primary tone; meets WCAG AA.

Token bindings

TokenCategoryUsage
backgroundcolorApp shell root surface (bg-background)
foregroundcolorApp shell root text color (text-foreground)
sidebarcolorSidebar surface, and the top-bar surface composed from Header (bg-sidebar)
sidebar-foregroundcolorSidebar and top-bar text color
sidebar-accentcolorSidebar active/hover item background
sidebar-bordercolorSidebar and top-bar chrome borders
6 (24px) / 4 (16px)spacingMain padding (p-6) and top-bar padding (px-4 md:px-6)
16 (64px)spacingTop-bar height (inherited from Header primitive)
Recent changes
  • fixed

    AppShell no longer nests two main landmarks

    2026-08-19

  • new

    Compound components join the catalog

    2026-07-09

[!NOTE] Compound component — an opinionated composition of Helix primitives. Composes: Sidebar, Collapsible.

Import

import { AppShell, AppShellTopBar } from "@timelycare/helix-ui"

Usage

<AppShell
  topBar={
    <AppShellTopBar
      logo={<Logo />}
      notifications={<NotificationBell />}
      userMenu={<UserMenu />}
    />
  }
  sidebar={
    <AppSidebar
      brand={<Logo />}
      items={navItems}
      activeItemId={currentRouteId}
      onItemClick={(id) => router.push(routes[id])}
    />
  }
>
  <PageHeader title="Members" />
  <DataTable columns={cols} data={rows} />
</AppShell>