Navigation

Horizontal tabs for switching between views within the same context. Uses a render-prop pattern so panel content is always fully controlled by the caller.

Preview

Project Alpha is a design system documentation site built with Next.js and Tailwind CSS. It defines tokens, components, and guidelines for the Sitka design language.

12
Files
3
Contributors
28d
Last updated

Anatomy

The tab bar sits flush to a bottom border. The active indicator is a 2px accent underline anchored to the bottom of each tab button. Badges use the accent-subtle token.

PartElementToken
Tab barrole=tablist divborder-b border
Tab buttonrole=tab buttontext-tertiary → text-primary on active
Active indicatorspan ::afterh-[2px] bg-accent rounded-t-full
Badgespanbg-accent-subtle text-accent
Panelrole=tabpanel divpt-4

Implementation

The children render prop receives the active tab id, making panel content fully flexible with no hidden DOM toggling.

Tabs.tsx
tsx
import { Tabs } from "@/components/ui/Tabs";

<Tabs
  tabs={[
    { id: "overview",  label: "Overview" },
    { id: "versions",  label: "Versions", badge: "3" },
    { id: "settings",  label: "Settings" },
  ]}
>
  {(active) => (
    <div>
      {active === "overview"  && <p>Overview panel content.</p>}
      {active === "versions"  && <p>Versions panel content.</p>}
      {active === "settings"  && <p>Settings panel content.</p>}
    </div>
  )}
</Tabs>

Props

PropTypeDefaultDescription
tabsTab[]Array of { id, label, badge? } objects defining each tab.
defaultTabstringid of the tab active on first render. Defaults to the first tab.
children(activeId: string) => ReactNodeRender prop that receives the active tab id and returns the panel content.

Mobile

Tab bars on mobile face overflow and touch-target challenges. Keep tabs to 5 or fewer and consider alternative navigation patterns for deeper hierarchies.

9:41

Repository

Project Alpha

v2.4.1 · Production

Last deploy 2h ago

3 members

ScenarioGuidance
OverflowMore than 5 tabs will overflow on mobile. Use overflow-x: auto with scroll-snap for scrollable tabs, or collapse extras into an overflow menu.
Touch targetsTab items need at least 44px height. Add extra vertical padding on mobile — py-3 instead of py-1.5.
Bottom tab barFor app-like navigation, position the tab bar fixed at the bottom with padding-bottom: env(safe-area-inset-bottom). This matches iOS and Android conventions.
Icon + labelOn bottom bars, pair each tab with an icon above the label. Icon-only tabs are harder to parse; label-only tabs miss the scanning affordance of icons.
Active indicatorUse a filled background or underline at least 3px tall. Thin indicators are hard to distinguish at arm's length on mobile.

Accessibility

  • Tab buttons use role=tab and aria-selected. The container uses role=tablist.
  • Each panel uses role=tabpanel. Wire aria-controls and aria-labelledby for full ARIA compliance.
  • Keyboard: Left/Right arrows should cycle tabs. Tab key moves focus to the active panel. Implement with onKeyDown on the tablist.
  • Focus ring is visible only with keyboard navigation via focus-visible.
  • Badges are decorative counts — add aria-label to the tab button to include the count in the accessible name when relevant.