Nav
A site's or app's navigation, such as a sidebar. Nav.Root is a nav landmark. Each Nav.Group is a list, named by its label when it has one, and each Nav.Item is a list item holding a Nav.Link. A Nav.Branch is a native <details> whose toggle opens a nested list of items.
On this page
<Nav.Root aria-label="Docs">
<Nav.Group label="Components">
<Nav.Item>
<Nav.Link href="/button">Button</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link href="/field" aria-current="page">
Field
</Nav.Link>
<Nav.Branch label="Field pages" open>
<Nav.Item>
<Nav.Link href="/field/select">Select</Nav.Link>
</Nav.Item>
</Nav.Branch>
</Nav.Item>
</Nav.Group>
</Nav.Root>
Each link and each branch toggle is its own Tab stop. Nav has no arrow keys and no selection, so it is not a Tree.
Current page
Mark the link to the page you are on with aria-current="page". Any value other than empty or false draws the link as the current row, from the nav.item.bg.current, nav.item.fg.current and nav.item.font.current-weight tokens. Your router sets the attribute.
Nav.Link takes render the way Link does, so a router's link renders the row with every prop it would have put on the <a>, its class included:
import NextLink from 'next/link';
<Nav.Link
href="/billing"
aria-current={pathname === '/billing' ? 'page' : undefined}
render={({ href, ...props }) => (href ? <NextLink href={href} {...props} /> : <a {...props} />)}
>
Billing
</Nav.Link>
An icon before the link text takes the nav.icon.size token.
Branches
A Nav.Branch after a Nav.Link in the same item holds that page's child pages. The link keeps its row, and the toggle is a chevron at the end of the row, so its label is visually hidden and only names it. Name the child pages, such as "Field pages", not the link's own text. A Nav.Branch on its own in an item shows its label as the toggle's text, for a group of pages with no page of its own.
The branch is a native <details>. open sets whether it is open when it renders and whenever the prop changes: true opens it, such as when navigation moves into the branch, and false closes it. In between, the user opens and closes it; follow that with onToggle. The nested list is indented by nav.indent under a rule drawn from nav.rule.color and nav.rule.width.
The chevron is a glyph: set chevron in GlyphsProvider to draw it with your icon library. Draw it pointing down; a closed branch turns it toward the end of its row.
Accessible composition
Todae handles: A nav landmark. Each group is a ul role="list" named through aria-labelledby by its label, and each item a list item. A branch is a <details> whose <summary> the browser exposes as a button with its expanded state, named by label. After a link, the label is visually hidden but still names the toggle. The chevron is aria-hidden. Focus rings are drawn inside the row, so a scroll container that clips the row's sides keeps them whole.
You supply: A name for the landmark through aria-label or aria-labelledby when the page has more than one nav. aria-current="page" on the link to the page you are on. A label on every branch. After a link, a label that says what the toggle shows, such as "Field pages".
Watch for: A current page marked by style alone, without aria-current. A branch label that repeats the link's text: a screen reader then reads two controls with one name. Controls other than links and branches inside a nav, such as a search field, which belong outside the list.
When to use
When: Navigation between the pages of a site or app, in a sidebar, a drawer or a menu panel, with groups and a level or two of child pages.
When not: The trail above the current page → Breadcrumbs. Sections of one page → Tabs. Browsing a hierarchy people open level by level, such as files → Tree. A list of actions → Menu.
Props
| Name | Type | Description |
|---|---|---|
aria-label | string | Landmark name. Give one when the page has more than one nav, such as "Docs" beside a header nav. |
children | React.ReactNode | Nav.Group elements. |
| Name | Type | Description |
|---|---|---|
label | React.ReactNode | Text shown above the list. It also names the list for screen readers. |
aria-label | string | Names the list when it has no label, or in place of it. aria-labelledby goes on the list too. |
children | React.ReactNode | Nav.Item elements. |
| Name | Type | Description |
|---|---|---|
children | React.ReactNode | A Nav.Link, a Nav.Branch, or a Nav.Link followed by the Nav.Branch of its child pages. |
| Name | Type | Description |
|---|---|---|
href | string | Where the link goes. |
render | (props: React.ComponentPropsWithRef<"a">) => React.ReactElement | Renders something other than a plain a, such as a router link. It receives every prop, the class name included, and must pass them to an a. |
aria-current | "page"| "step"| "location"| "date"| "time"| "true"| "false" | Marks the link to the page you are on, which draws it as the current row. |
children | React.ReactNode | The link text, with an optional icon before it. |
| Name | Type | Description |
|---|---|---|
label Required | React.ReactNode | The toggle's text. After a Nav.Link in the same item, the toggle shows only a chevron and this text only names it, so name the child pages, such as "Field pages". |
open | boolean | Sets whether the branch is open when it renders and whenever the prop changes: true opens it, false closes it. In between, the user opens and closes it; follow that with onToggle. |
onToggle | (event: React.ToggleEvent<HTMLDetailsElement>) => void | Called after the branch opens or closes. |
children | React.ReactNode | Nav.Item elements, the child pages. |