The sidebar
Add a page to the menu, group it with its neighbours, give it an icon and a badge. It is one file, and the shell, the routes list and the tests all read it.
Before you start
This describes the React edition on Tailwind CSS, Pro tier. The file is app/nav.ts in your download, and the types come from @viliha/vui-core, which the download resolves through the same alias as the components.
Add a page to the sidebar
NAV is an array of groups. A group is a heading and its rows; a row is a label, an icon and either an address or a submenu.
import type { FreeNavGroup } from "@viliha/vui-core";
export const NAV: readonly FreeNavGroup[] = [
{
heading: "Records",
entries: [
{ label: "Customers", icon: "Users", href: "/customers" },
{
label: "Orders",
icon: "Cart",
children: [
{ label: "All orders", href: "/orders" },
{ label: "Add order", href: "/orders/new", icon: "Plus" },
],
},
],
},
];Check it worked
- The row appears under its heading, in the order you wrote it. Nothing sorts the list.
- The icon is the glyph you named, not a plain square. A square means the name did not resolve, which is the first symptom below.
- The breadcrumb names it when you open the page, because the trail is derived from this list rather than typed on the page.
The shape, in full
A group is a titled band
{ heading, entries }. The band is always open; there is no collapse at this level. Use it to cluster related pages under a label.
A row is a link or a submenu, never both
{ label, icon, href?, children? }. Give it an href and it is a link. Give it children and it is a collapsible submenu whose rows carry the addresses. Those are the only two shapes, so a third level is not something to hand-roll.
A badge is a word beside the label
badge takes any short string, on a row or on a submenu row. The app defines two it uses, PRO_BADGE and NEW_BADGE, and nothing stops you adding your own.
An icon is a name, not a component
icon is the export name as a string: "Users", never {Users}. That is what lets one navigation list drive four framework editions, since a React component in this file could not be read by the Vue one. The shell maps the name through SHELL_NAV_ICONS in @viliha/vui-react/voilet-shell, which is also the list of names available to you.💡 A submenu row inherits its parent's icon
iconoff a child and it draws its parent's. Set one only when it says something the parent's does not, the way an “Add” row is a plus. This is why most children in the shipped menu carry no icon at all.When it did not work
The row draws a plain square
Cause: the icon name did not resolve, and the shell falls back to Box rather than crashing. That fallback is deliberate, and it is also why a typo is quiet: the sidebar keeps working and one row is a square. Fix: check the spelling against SHELL_NAV_ICONS, which is an ordinary exported object you can open. Verify: the glyph appears where the square was.
The link 404s
Cause: the href has no route behind it. The sidebar does not create pages; it points at them. Fix: add the route under app/, or correct the address. Verify: the row opens its page.
The page exists and nothing links to it
Cause: the route was added and the menu was not, so the page is reachable only by typing its address. Fix: add the row. Verify: the page is reachable from the sidebar. This is the half that is easy to forget, because nothing breaks: the page works perfectly for anyone who already knows its address.
The sidebar renders nothing after an edit
Cause: NAV is an array of groups, and a flat array of rows type-checks against nothing it draws. Fix: wrap the rows in { heading, entries: [...] }. Verify: the band and its rows come back.
What else reads this file
Adding a row is not only a menu change, which is the point of keeping it in one place.
NAV_HREFSis derived fromNAV, so every address in the menu is already in the list your tests walk. It is not a second list to maintain.- The breadcrumb trail is built from the same tree, so a renamed label moves in both places at once.
- The command palette offers what the menu offers.
Next
The menu is yours. Shells covers the frame around it, and Layouts covers how a page fills the space inside it.