NavTree
NavTree turns a flat route list into a collapsible sidebar navigation tree.
Source
LLM docs
Package
Examples
4
Properties
42
Playground
Basics
#
A flat list of routes becomes a nested sidebar. The group above the active route is already open, because
activeHref opened it.Loading demo…
import React, { useState } from 'react';
import { NavTree, type NavTreeItem } from '@plocks/ui';
// The whole input: a flat list, with a category on each row. Nothing here
// describes the tree — `NavTree` derives it.
const ROUTES: NavTreeItem[] = [
{ label: 'Getting Started', href: '/getting-started' },
{ label: 'Button', href: '/components/Button', group: ['Components', 'Input'] },
{ label: 'Select', href: '/components/Select', group: ['Components', 'Input'] },
{ label: 'Checkbox', href: '/components/Checkbox', group: ['Components', 'Input'] },
{ label: 'Card', href: '/components/Card', group: ['Components', 'Display'] },
{ label: 'Badge', href: '/components/Badge', group: ['Components', 'Display'] },
{ label: 'Tabs', href: '/components/Tabs', group: ['Components', 'Navigation'] },
];
export function Demo() {
const [route, setRoute] = useState('/components/Select');
return (
<NavTree
items={ROUTES}
activeHref={route}
onNavigate={item => setRoute(item.href)}
showGuides
/>
);
}Counts and order
#
groupOrder curates the sections that matter and leaves the rest alphabetical. renderEndSection hangs a count off each branch, and openDepth={0} starts everything closed.Loading demo…
import React, { useState } from 'react';
import { Badge, NavTree, type NavTreeItem } from '@plocks/ui';
const ROUTES: NavTreeItem[] = [
{ label: 'Button', href: '/components/Button', group: 'Input' },
{ label: 'Select', href: '/components/Select', group: 'Input' },
{ label: 'Checkbox', href: '/components/Checkbox', group: 'Input' },
{ label: 'Card', href: '/components/Card', group: 'Display' },
{ label: 'Badge', href: '/components/Badge', group: 'Display' },
{ label: 'Tabs', href: '/components/Tabs', group: 'Navigation' },
];
export function Demo() {
const [route, setRoute] = useState('/components/Card');
return (
<NavTree
items={ROUTES}
activeHref={route}
onNavigate={item => setRoute(item.href)}
// Curate the order that matters and let the rest sort themselves.
groupOrder={['Input', 'Display']}
openDepth={0}
renderEndSection={node =>
node.children ? <Badge size="xs" variant="light">{node.children.length}</Badge> : null
}
/>
);
}Filtering
#
searchable adds a filter field wired to the tree. Typing hides the rows that do not match, opens the branches above the ones that do, and marks the matched substring — past a certain length, three letters beat any amount of nesting.Loading demo…
import React, { useState } from 'react';
import { NavTree, type NavTreeItem } from '@plocks/ui';
const ROUTES: NavTreeItem[] = [
{ label: 'Button', href: '/components/Button', group: 'Input' },
{ label: 'Checkbox', href: '/components/Checkbox', group: 'Input' },
{ label: 'Select', href: '/components/Select', group: 'Input' },
{ label: 'TextArea', href: '/components/TextArea', group: 'Input' },
{ label: 'Badge', href: '/components/Badge', group: 'Display' },
{ label: 'Card', href: '/components/Card', group: 'Display' },
{ label: 'Breadcrumbs', href: '/components/Breadcrumbs', group: 'Navigation' },
{ label: 'Tabs', href: '/components/Tabs', group: 'Navigation' },
];
export function Demo() {
const [route, setRoute] = useState('/components/Card');
return (
<NavTree
items={ROUTES}
activeHref={route}
onNavigate={item => setRoute(item.href)}
searchable
searchPlaceholder="Filter components…"
/>
);
}Collapsed rail
#
collapsed drops the sidebar to a strip of top-level icons — the group holding the current route stays marked, and pressing one lands on the first page inside it. A sidebar with a hundred routes shows a handful of icons here, not a hundred.Loading demo…
import React, { useState } from 'react';
import { Column, Icon, NavTree, Switch, type NavTreeItem } from '@plocks/ui';
const ROUTES: NavTreeItem[] = [
{ label: 'Button', href: '/components/Button', group: 'Components' },
{ label: 'Card', href: '/components/Card', group: 'Components' },
{ label: 'LineChart', href: '/components/LineChart', group: 'Charts' },
{ label: 'BarChart', href: '/components/BarChart', group: 'Charts' },
{ label: 'useHover', href: '/hooks/useHover', group: 'Hooks' },
];
const GROUP_ICONS = {
Components: <Icon name="grid" size={18} />,
Charts: <Icon name="chart-bar" size={18} />,
Hooks: <Icon name="hook" size={18} />,
};
export function Demo() {
const [collapsed, setCollapsed] = useState(true);
const [route, setRoute] = useState('/components/Card');
return (
<Column gap="md">
<Switch checked={collapsed} onChange={setCollapsed} label="Collapsed" />
<NavTree
items={ROUTES}
activeHref={route}
onNavigate={item => setRoute(item.href)}
groupIcons={GROUP_ICONS}
collapsed={collapsed}
/>
</Column>
);
}plocks
plocks
React Native design for iOS, Android, and Web.
Quick Links
Resources