Skip to content

Sidebar ​

The app's main navigation, down the left of the page, optionally collapsing to icons.

Open in Storybook to try every prop.

When to use ​

  • Moving between the main areas of an app.

When not to use ​

  • Actions on the current page (use Buttons), or navigation within a page (tabs).

Anatomy ​

PartRequiredNotes
headerNoTop, same height as Header (e.g. logo).
bodyYesScrolling list of sections.
section labelNoSmall bold uppercase heading. A divider when collapsed.
itemYesIcon, label, optional badge. Items with children show › at the right.
back rowNested onlyFirst row inside a nested item: ‹ and its label (bold), then a divider. Goes up one level.
footerNoBottom, e.g. profile or help. Holds the collapse button.

Properties ​

PropertyTypeDefaultDescription
itemsSidebarItem[][]Items in one unlabelled section.
sections{ label?, items }[][]Labelled groups, after items.
collapsedboolean (v-model)falseNarrow, icons only.
collapsiblebooleanfalseShow the collapse / expand button.
labelstring"Main"Name of the navigation landmark.
linkComponentstring | Component"a"Used for items with to, e.g. RouterLink.
ui—Extra classes per part.

SidebarItem: label, icon, href or to, active, badge, disabled, onSelect, children (sub-pages).

Helper: sidebarTrail(items, sections) returns the path to the active item (top first), e.g. for a Breadcrumb.

Sizes ​

SizeWidthItem heightItem padding (x)GapIcon sizeText style
Expandedspacing 64 (256px)38px (md Button)spacing 3spacing 316pxsm
Collapsedspacing 14 (56px)38pxcentred—16pxlabels hidden

States ​

StateAppearanceNotes
Enabledtransparent, black text
Hoveredblack 10%Web / desktop only
Pressedblack 15%
Active (current page)primary 10%, primary bold textaria-current="page"
Parent of current pageprimary bold text, no fillNested: "you are in here"
Focusedinset focus ringInset so the scrolling body doesn't clip it
Disabled40% opacity, not-allowed cursorLinks lose their href

Behaviour ​

  • Collapse: width animates over 300ms ease-out; none with reduced motion.
  • Scrollbar: thin when expanded; hidden when collapsed (the 40px strip can't spare the width), the list still scrolls.

Nested items ​

ActionResult
Choose an item with children (›)Shows its children in place of the list, with a back row at the top. Doesn't navigate or emit select
Choose the back rowGoes up one level
After opening a levelFocus on its first item
After going backFocus on the item just left
Current page changesThe list opens at that page's level
CollapsedBack row shows only ‹ (tooltip "Back from {label}"); give sub-pages icons too
In the mobile SlideoverOpening and going back keep it open; choosing a page closes it
  • Small screens: the app hides it and shows it inside a Slideover (left) opened from the Header's menu button. Choosing an item should close the Slideover.

Accessibility ​

  • <aside> containing a <nav> named by label.
  • Each labelled section is a list named by its heading.
  • Collapsed: labels and badges stay in the DOM for screen readers; the label is also a native tooltip (title).
  • Nested: items with children are buttons announced as "{label}, more options"; the back row as "Back from {label}".
  • Icons are hidden from assistive tech.

Platform adaptation ​

PlatformImplementationDifferences
Web / desktop (Vue)Sidebar.vue—
iOS (SwiftUI)—NavigationSplitView sidebar (iPad), tab bar (iPhone).
Android (Compose)—NavigationDrawer / NavigationRail.

Tokens used ​

TokenUsed for
color whitebackground
color black (15%)border, dividers
color primary (10% / 15%)active item, active hover
color focusfocus ring

Do / Don't ​

DoDon't
Give every item an icon if it can collapse, sub-pages included.Nest so deep people lose their place: pair nesting with a Breadcrumb.
Use labels that are unique within each level.Repeat a label at the same level (levels are tracked by label).

Released under the PolyForm Noncommercial License 1.0.0.