Skip to main content

TabNav

Moleculebeta@macaw-ds/ui · since 0.1.0

Page-level tabs where each tab is its own URL. Not for switching panels in place; that is Tabs (not in the system yet).

When to use

The sections of one page that each deserve their own URL: a component's Overview, Usage, Accessibility and Code; an invoice's Overview, Line items, Payments and Activity. People can bookmark a tab, share it, and open it in a new browser tab.

When not to use

  • Switching panels in place without changing the URL, such as a chart's Daily, Weekly and Monthly views. That is Tabs, which is not in the system yet. Report not-in-system: Tabs.
  • Primary navigation between unrelated areas of the product. That is SideNav.

Anatomy

A nav landmark with a list of links. The active link has a 2px underline in the primary action colour and primary text. Inactive links are secondary text and show a strong underline on hover.

With a router

<TabNav aria-label="Invoice sections">
  <TabNav.Item asChild active={pathname === '/invoices/41'}>
    <Link href="/invoices/41">Overview</Link>
  </TabNav.Item>
</TabNav>

Accessibility

  • These are links, not role="tab". Each is a Tab stop and Enter follows it. Arrow keys belong to in-page Tabs.
  • The current tab has aria-current="page". The underline is not the only signal, because the text colour changes too.
  • The landmark needs aria-label, and TypeScript requires it.

Do and don't

Do

  • Place TabNav in PageHeader's tabs slot so it sits under the page title and sticks below the TopBar.

Don't

  • Do not use TabNav to swap panels without changing the URL.

  • Do not nest TabNav inside a Card.

Rules

Hard rules an agent must follow. The reviewer agent and the lint config check the ones that can be checked.

  1. Each tab is a URL. Use TabNav only when the sections are separate pages or routes.
  2. Exactly one item is active, and it matches the current route.
  3. Keep labels to one or two words; four to six tabs at most.
  4. With a router, pass the Link through asChild so client-side navigation keeps working.