Skip to content
Cynosurev0.5.0
SearchStorybookGitHubnpmColour mode: System. Switch to Light

Components / Structure

SideNav

Vertical navigation for an app or a docs site: links in titled sections, groups that expand, the current page marked, and a collapsed icon rail.

Marks the current page

When to useLink to "When to use"

  • The main navigation of an app or site with more than a handful of destinations.

Use something else when:

  • there are two to five peer views on one page: Tabs
  • the choices are commands, not places: Menu
  • you need the whole layout with a top bar and a small-screen drawer: AppShell, which holds a SideNav

UsageLink to "Usage"

tsx
Copy
import { SideNav } from "@arshad-shah/cynosure";

<SideNav label="Main" items={items} current={route.id} />

An item with href is a link; without it, a button that calls onNavigate. An item with items is a group. Entries with a label and items but no id are sections with a heading.

RoutersLink to "Routers"

renderLink renders each link with your router; spread the props it receives (href, className, aria-current, onClick, children). Or keep plain links and call event.preventDefault() in onNavigate to route on the client.

tsx
Copy
<SideNav renderLink={({ href, ...props }) => <RouterLink to={href} {...props} />} … />
Works with your router

GroupsLink to "Groups"

Groups open and close with a click, Enter or Space. A group holding the current page opens by itself, also when the page changes.

Groups open on the way to the current page

Collapsed railLink to "Collapsed rail"

collapsed shows icons only. Labels appear in tooltips on hover and keyboard focus and stay the accessible names. Give top-level items icons. Groups do not open inside the rail; choosing one calls onCollapsedChange(false) and opens the group, so the navigation can widen.

Rail expands to the group

In the rail an item's badge shrinks onto the top inline-end corner of its icon, and stays part of the item's name ("Search 12").

Activity barLink to "Activity bar"

A collapsed SideNav on its own, without AppShell, is an editor's activity bar. footer takes entries pinned to the bottom (Settings, a notifications action): the same items, sections and actions as items, after them in the Tab order. With a footer the navigation fills its container's height. placement="end" is for a rail on the inline end of the window, such as a secondary activity bar: the current item's indicator moves to the side facing the content and the tooltips open towards it.

tsx
Copy
<SideNav
  label="Views"
  collapsed
  current={view}
  onNavigate={setView}
  items={[
    { id: "explorer", label: "Explorer", icon: database },
    { id: "search", label: "Search", icon: search, badge: <Badge tone="accent">12</Badge> },
  ]}
  footer={[
    { kind: "action", id: "notify", label: "Notifications", icon: bell, onClick: openNotifications },
    { id: "settings", label: "Settings", icon: settings },
  ]}
/>

Bottom barLink to "Bottom bar"

layout="bar" lays the top-level items out in one row, icon over label, for a phone's tab bar (AppShell's tabs). Section headings are left out, and a group is one item that calls onNavigate with its own id. Give it three to five items, each with an icon.

A primary actionLink to "A primary action"

One top-level entry may be an action instead of a destination: { kind: "action", id, label, icon, onClick }. In the bar it is a round accent button raised above the row, for a phone's "Add". It sits where it is placed in items, so put it in the middle of an odd number of entries to centre it.

tsx
Copy
import { home, inbox, plus } from "@arshad-shah/cynosure/icons";

<SideNav
  label="Sections"
  layout="bar"
  current={route.id}
  items={[
    { id: "today", label: "Today", icon: home },
    { kind: "action", id: "add", label: "Add", icon: plus, onClick: openNewTask },
    { id: "inbox", label: "Inbox", icon: inbox },
  ]}
/>
Bar with aprimary action

In the column the same entry is a full-width accent Button with the icon and the label, and in the collapsed rail an accent IconButton. Give a navigation one action at most and keep it at the top level, not inside a section or a group.

Action in the column and the rail

PropsLink to "Props"

Every prop of SideNav
PropWhat it doesTypeDefault
labelRequired
Accessible name of the navigation landmark, e.g. "Main".string
itemsRequired
Items and sections ({ label, items }), in order. One entry may be a primary action ({ kind: "action", id, label, icon, onClick }): a button that sits where it is placed.NavEntry[]
current
id of the page being shown: marked aria-current="page"; groups holding it start expanded.string
onNavigate
Called when an item is chosen (links too: call event.preventDefault() to route on the client).((id: string, event: MouseEvent<HTMLElement>) => void)
renderLink
Render links with your router: (props) => <RouterLink to={props.href} {...props} />. Default: a plain link.((props: NavLinkProps) => ReactElement)a plain link
collapsed
Icon-only rail; labels show in tooltips and stay the accessible names. Give top-level items icons. Groups do not open inside the rail: choosing one calls onCollapsedChange(false) so you can widen the navigation.boolean
onCollapsedChange
Called with false when a group is chosen in the collapsed rail.((collapsed: boolean) => void)
defaultExpanded
Group ids expanded at first (groups holding current always are).string[]
layout
column (default): the vertical navigation. bar: one row of top-level items, icon over label, for a phone's bottom tab bar (AppShell's tabs). Section headings are left out, and a group is one item that calls onNavigate with its id (a bar has no room to open it). Give a bar three to five items, each with an icon."column" | "bar"
size
Height and text size of the items. Default "md".Size"md"
placement
Which side of the content the navigation sits on. start (default): the inline start, as a left sidebar or activity bar. end: the inline end, as a secondary activity bar on the right; the current item's indicator moves to the inline end, facing the content, and the rail's tooltips open towards the inline start."start" | "end"
footer
Entries pinned to the bottom of the navigation, such as Settings or a notifications action: items, sections and actions, as in items, and in the same Tab order after them. The navigation then fills its container's height. In a bar they end the row.NavEntry[]
className
Added to the root element, for layout.string
style
Layout only. Restyle through --cy-side-nav-* tokens.CSSProperties

Generated from the type definitions in @arshad-shah/cynosure 0.5.0.

Behaviour and accessibilityLink to "Behaviour and accessibility"

  • Landmark: a <nav> named by label. Each section is a list named by its heading.
  • Current page: aria-current="page", the accent's soft fill and a bar on the inline start (the inline end with placement="end").
  • Keyboard: Tab and Shift+Tab move between items, then the footer's; Enter follows a link; Enter or Space presses a button or toggles a group (aria-expanded). There is no arrow-key roving: every item is a Tab stop, as in any list of links.
  • Disabled items are aria-disabled buttons that do nothing.
  • Primary action: a button named by its label. It is not a link, is never aria-current (even when its id equals current) and does not call onNavigate. It is one Tab stop in its place among the items; Enter or Space presses it. In the bar and the rail the label also shows as a tooltip.
  • Touch: rows are at least 44px tall on touch screens (24px with a mouse). The bar's action button is 48px across by default, and its hit area is never under 44px on touch screens.
  • Contrast: items, headings and the current item keep 4.5:1 in light and dark modes.
  • Direction: indents, the current bar, placement and the rail's badge follow the text direction.

ThemingLink to "Theming"

Theming, table 1
TokenSets
--cy-side-nav-widthWidth of the expanded navigation (240px)
--cy-side-nav-rail-widthWidth of the collapsed rail
--cy-side-nav-radiusCorner radius of item highlights
--cy-side-nav-action-sizeDiameter of the round action button in the bar (48px)
--cy-side-nav-action-liftHow far the action button rises above the bar's row (8px)
ts
Copy
createTheme({ components: { sideNav: { width: "280px" } } });

AppShell · Tabs · Button · IconButton · Tooltip

These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.

Getting started
Theming
Icons
Accessibility
Code editors and highlighters
Using Cynosure without React
Build-tool adapters
Components
Accordion
AppShell
AspectRatio
Breadcrumbs
Calendar
Card
Container
Disclosure
Divider
DocumentViewport
Grid
Inline
PageHeader
PanZoomCanvas
ScrollArea
SideNav
SortableList
SplitPane
Stack
StatusBar
Tabs
TitleBar
Toolbar
Tree
VirtualList
ZoomControls
Announcer
Avatar
Badge
CodeBlock
DescriptionList
EmptyState
ErrorBoundary
Field
Heading
IconTile
Image
Kbd
List
LoadingDots
Notice
ProgressBar
Prose
Skeleton
Stat
Steps
Table
Text
Button
Checkbox
Checklist
ColorField
ColorSwatches
Combobox
CompareSlider
CopyButton
DateField
FileDrop
IconButton
KeyValueEditor
Link
NumberField
Pagination
PathField
RadioGroup
SearchField
SegmentedControl
Select
SelectionFrame
SignaturePad
Slider
Switch
TagInput
TextArea
TextField
ThemeToggle
TimeField
ButtonBase
ChoiceBase
DialogBase
FileInputBase
HiddenValue
Icon
InputBase
LinkBase
LiveRegion
TextAreaBase
ChatMessage
Choice
DataGrid
DeviceFrame
HighlightedText
HotkeyField
ListRow
OptionList
OverlayFrame
QRCode
RowActions
Section
SiteFooter
SiteHeader
SkipLink
SortButton
VisuallyHidden
CommandPalette
ContextMenu
Dialog
FloatingToolbar
HoverCard
Menu
Menubar
Popover
ProgressOverlay
Sheet
Toast
Tooltip
Changelog