Skip to content

Headless Core — Sidebar

Powers Sidebar internally. Compare with Navbar: same active-item-tracking core, but oriented vertically (ArrowUp/ArrowDown instead of ArrowLeft/ArrowRight), with a collapse toggle instead of scroll-spy/route detection, and items split into main/bottom sections instead of a single flat list.

import { createSidebar } from '@andersseen/headless-components/sidebar';
const sidebar = createSidebar({
items: [
{ id: 'home', label: 'Home', icon: 'home' },
{ id: 'settings', label: 'Settings', icon: 'settings', section: 'bottom' },
],
defaultActiveItem: 'home',
onActiveItemChange: id => console.log('Active:', id),
});
sidebar.actions.toggleCollapse();
OptionTypeDefaultNotes
itemsSidebarItemDef[][]{ id, label, icon?, disabled?, section? }; section is 'main' (default) or 'bottom'.
defaultActiveItemstring'home'
onActiveItemChange(id: string) => void
defaultCollapsedbooleanfalse
onCollapsedChange(collapsed: boolean) => void
mobileCollapsebooleantrueOnly affects the initial mobileCollapsed state — see below.
ariaLabelstring'Sidebar navigation'

activeItem, collapsed, mobileCollapsed — note mobileCollapsed is initialized from config.mobileCollapse !== false but nothing in this factory ever changes it afterward except actions.setMobileCollapsed; actually detecting “we’re on mobile” (a media query) is the consuming component’s job.

setActiveItem(id), toggleCollapse() / setCollapsed(v), setMobileCollapsed(v), setItems(items) (re-registers the list; doesn’t reset activeItem the way Navbar’s setItems does).

isActive(id), isCollapsed(), isMobileCollapsed(), isDisabled(id), getMainItems() (items with no section or section: 'main'), getBottomItems() (section: 'bottom'), getItemIds().

GetterReturns
getContainerProps()role: 'navigation', aria-label, data-collapsed, data-mobile-collapsed
getNavListProps(section?)role: 'menu', aria-label ('Main navigation' or 'Secondary navigation' depending on section)
getItemProps(id)role: 'menuitem', aria-current, aria-disabled, data-active, data-state, data-collapsed, tabindex, id (sidebar-item-${id})
getToggleProps()aria-expanded (inverse of collapsed), aria-label (swaps “Expand”/“Collapse”), data-collapsed, role: 'button', tabindex: 0

handleItemKeyDown(event, currentItemId)ArrowDown/ArrowUp move with wraparound (skipping disabled items), Home/End jump, Enter/Space activate. No Escape handling (unlike Navbar — a sidebar has no mobile overlay panel to dismiss with it).

Go to "settings" Toggle collapse

Navbar — the horizontal counterpart, with scroll-spy and route-based active detection.