NavigationMenu
A site's navigation: a row of destinations, some of which open a panel of more of them. Every row is a real link, which is what puts it in the link list, on the status bar and in a crawler's index.
import { NavigationMenu, NavigationMenuItem, NavigationMenuLink } from 'neba';
<NavigationMenu aria-label="Main">
<NavigationMenuItem label="Product">
<NavigationMenuLink href="/analytics" title="Analytics" description="Every number." />
</NavigationMenuItem>
<NavigationMenuItem label="Pricing" href="/pricing" />
</NavigationMenu>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | The height and type scale of the items, and of the links in the panels |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | The role the open item and the panel edge carry |
| densityshared | 'default' | 'compact' | 'default' | Horizontal padding only |
| orientationshared | 'horizontal' | 'vertical' | 'horizontal' | Which way the row runs. Vertical is a nav rail whose panels open beside it |
| value | string | null | — | Which item's panel is open, by its value. Nullish means closed |
| defaultValue | string | null | — | Which starts open, uncontrolled |
| onValueChange | (value: string | null) => void | — | Fired whenever the open item changes |
| delay | number | 50 | How long the pointer rests before a panel opens, in milliseconds |
| closeDelay | number | 50 | How long a panel stays after the pointer leaves, in milliseconds |
| sideOffset | number | 8 | Distance from the row, in pixels |
| children | ReactNode | — | The items |
Every native <nav> attribute passes through, apart from color.
The difference from a Menu is what the rows are. A menu holds actions, so its rows are menuitems. This holds links, so it is a <nav> full of <a>s. Reach for a Menu when the row does something and for this when the row goes somewhere.
NavigationMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
| label * | ReactNode | — | The word in the row |
| href | string | — | Makes the item a plain link rather than something that opens a panel. An item with an href and no children is a destination, and it is announced as one |
| target | string | — | Where the link opens. Ignored without href. Anything other than this tab also gets noopener noreferrer added to rel |
| rel | string | — | The link's rel. Merged rather than replaced, so writing nofollow does not take the protection off a link that still opens elsewhere |
| startIcon | ReactNode | — | Content before the label |
| value | string | — | Identifies the item, for a controlled menu |
| disabled | boolean | false | Unavailable. The word stays in the row and opens nothing |
| columns | number | 1 | How many columns the panel lays its links out in |
| children | ReactNode | — | The panel's contents: usually NavigationMenuLinks |
NavigationMenuLink
| Prop | Type | Default | Description |
|---|---|---|---|
| href * | string | — | Where it goes |
| title * | ReactNode | — | The row's name |
| description | ReactNode | — | A second line under it, one step down the scale and muted |
| startIcon | ReactNode | — | A glyph before the title |
Examples
Items with and without a panel
An item with children is a trigger and a panel; an item with an href and nothing else is a link, and the two are announced differently.
columns
How many columns the panel lays its links out in. One is right for a short list; a wide menu of regions or products wants two or three.
import { NavigationMenu, NavigationMenuItem, NavigationMenuLink } from 'neba';
export default function NavigationMenuColumns() {
return (
<NavigationMenu aria-label="Regions" size="sm">
<NavigationMenuItem label="One column">
<NavigationMenuLink href="#seoul" title="Seoul" />
<NavigationMenuLink href="#tokyo" title="Tokyo" />
<NavigationMenuLink href="#osaka" title="Osaka" />
</NavigationMenuItem>
<NavigationMenuItem label="Three columns" columns={3}>
<NavigationMenuLink href="#seoul" title="Seoul" description="ap-northeast-2" />
<NavigationMenuLink href="#tokyo" title="Tokyo" description="ap-northeast-1" />
<NavigationMenuLink href="#osaka" title="Osaka" description="ap-northeast-3" />
<NavigationMenuLink href="#frankfurt" title="Frankfurt" description="eu-central-1" />
<NavigationMenuLink href="#dublin" title="Dublin" description="eu-west-1" />
<NavigationMenuLink href="#virginia" title="Virginia" description="us-east-1" />
</NavigationMenuItem>
</NavigationMenu>
);
}orientation
vertical stacks the items and opens the panels beside them: a nav rail rather than a bar.
import { NavigationMenu, NavigationMenuItem, NavigationMenuLink } from 'neba';
export default function NavigationMenuOrientation() {
return (
<NavigationMenu orientation="vertical" size="sm" aria-label="Sections" className="w-44">
<NavigationMenuItem label="Overview" href="#overview" />
<NavigationMenuItem label="Deploys">
<NavigationMenuLink href="#production" title="Production" />
<NavigationMenuLink href="#preview" title="Preview" />
</NavigationMenuItem>
<NavigationMenuItem label="Settings">
<NavigationMenuLink href="#general" title="General" />
<NavigationMenuLink href="#members" title="Members" />
</NavigationMenuItem>
</NavigationMenu>
);
}In a header
import {
AppLogo,
Button,
Header,
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink
} from 'neba';
export default function NavigationMenuHeader() {
return (
<Header
className="w-full"
brand={<AppLogo name="Neba" src="/128x128.png" size="sm" />}
actions={
<Button size="sm" variant="outline">
Sign in
</Button>
}
>
<NavigationMenu size="sm" aria-label="Main">
<NavigationMenuItem label="Product">
<NavigationMenuLink href="#analytics" title="Analytics" description="Every number." />
<NavigationMenuLink href="#pipelines" title="Pipelines" description="Build and ship." />
</NavigationMenuItem>
<NavigationMenuItem label="Docs" href="#docs" />
<NavigationMenuItem label="Pricing" href="#pricing" />
</NavigationMenu>
</Header>
);
}Accessibility
- Renders a real
<nav>; give it anaria-labelwhere a page holds more than one. - Every destination is an
<a href>, so it can be opened in a new tab, copied, followed by a crawler and reached from a screen reader's link list. - The row is keyboard-driven: the arrow keys move between items and into an open panel, and Escape closes it.
- A panel resizes between items rather than closing and reopening, so crossing the row reads as one surface.