TabNav
Moleculebeta@macaw-ds/ui · since 0.1.0Page-level tabs where each tab is its own URL. Not for switching panels in place; that is Tabs (not in the system yet).
Import
import { TabNav, TabNavItem } from '@macaw-ds/ui';Props
| Prop | Type | Default | Description |
|---|---|---|---|
| aria-label* | string | Page sections | Names the navigation landmark, e.g. "Invoice sections". Accessibility: Required: pages often have several nav landmarks. |
| active | boolean | false | On TabNav.Item: the current page. Accessibility: Sets aria-current="page". |
| asChild | boolean | false | On TabNav.Item: render a router Link instead of an anchor. |
| children | ReactNode | — | TabNav.Item elements. |
Slots
Named parts you pass content into.
- Item
Tokens used
Every value this component renders comes from one of these tokens.
| Token | Light | Dark |
|---|---|---|
| color.action.primary semantic · color | oklch(0.51 0.2 262) | oklch(0.51 0.2 262) |
| color.fg.primary semantic · color | oklch(0.15 0.012 250) | oklch(0.985 0.002 250) |
| color.fg.secondary semantic · color | oklch(0.38 0.012 250) | oklch(0.87 0.007 250) |
| color.line.strong semantic · color | oklch(0.53 0.012 250) | oklch(0.53 0.012 250) |
| size.control.lg semantic · dimension | 40px | 40px |
| text.size.label-md semantic · dimension | 0.875rem | 0.875rem |
| color.focus.ring semantic · color | oklch(0.51 0.2 262) | oklch(0.68 0.16 262) |
Eject
Ejecting copies the source into your repo and records it in .macaw/ejected.json, so the fork stays visible. The same file is served as a shadcn-compatible registry item.
npx @macaw-ds/cli add tab-nav --ejectnpx shadcn@latest add https://macaw-design-system.vercel.app/r/tab-nav.jsonBundle budget: 2,000 bytes gzipped, enforced by size-limit in CI.
Manifest
registry/manifests/tab-nav.json, the contract the playground, CLI, plugin and MCP server read.
{
"name": "TabNav",
"slug": "tab-nav",
"package": "@macaw-ds/ui",
"status": "beta",
"since": "0.1.0",
"level": "molecule",
"category": "navigation",
"purpose": "Page-level tabs where each tab is its own URL. Not for switching panels in place; that is Tabs (not in the system yet).",
"description": "Navigation tabs for the sections of a page: Overview, Usage, Accessibility, Code. Each item is a link, so tabs can be bookmarked, shared and opened in a new tab. The current tab sets aria-current=\"page\". Usually rendered by PageHeader's tabs slot.",
"exports": [
"TabNav",
"TabNavItem"
],
"props": {
"aria-label": {
"type": "string",
"default": "Page sections",
"required": true,
"description": "Names the navigation landmark, e.g. \"Invoice sections\".",
"a11y": "Required: pages often have several nav landmarks."
},
"active": {
"type": "boolean",
"default": false,
"required": false,
"description": "On TabNav.Item: the current page.",
"a11y": "Sets aria-current=\"page\"."
},
"asChild": {
"type": "boolean",
"default": false,
"required": false,
"description": "On TabNav.Item: render a router Link instead of an anchor.",
"control": "none"
},
"children": {
"type": "node",
"required": false,
"description": "TabNav.Item elements.",
"control": "none"
}
},
"slots": [
"Item"
],
"tokens": [
"color.action.primary",
"color.fg.primary",
"color.fg.secondary",
"color.line.strong",
"size.control.lg",
"text.size.label-md",
"color.focus.ring"
],
"a11y": {
"role": "navigation, containing a list of links",
"focusable": true,
"keyboard": [
"Tab: move between tabs (each is a link)",
"Enter: follow the link"
],
"name": "The navigation landmark takes its name from aria-label; each tab from its text.",
"notes": [
"Navigation tabs are links, not role=\"tab\". Arrow keys are not used; that pattern belongs to in-page Tabs.",
"The selected tab is marked with aria-current=\"page\", and the underline is not the only signal: the text colour changes too.",
"The list never scrolls horizontally, so the 2px focus ring is never clipped; keep to six short labels."
]
},
"motion": {
"description": "Underline and text colour ease on hover.",
"tokens": [
"duration.fast",
"ease.standard"
]
},
"budget": {
"gzip": 2000
},
"rules": [
"Each tab is a URL. Use TabNav only when the sections are separate pages or routes.",
"Exactly one item is active, and it matches the current route.",
"Keep labels to one or two words; four to six tabs at most.",
"With a router, pass the Link through asChild so client-side navigation keeps working."
],
"dos": [
"Place TabNav in PageHeader's tabs slot so it sits under the page title and sticks below the TopBar."
],
"donts": [
"Do not use TabNav to swap panels without changing the URL.",
"Do not nest TabNav inside a Card."
],
"examples": [
{
"name": "Default",
"props": {
"aria-label": "Page sections"
}
}
],
"registryDependencies": [],
"dependencies": [
"radix-ui"
],
"related": [
"page-header",
"side-nav"
]
}