Sidebar
Projects
4 active
- NKNeon v1.168%
- DSDocs search41%
- MSMarketing site90%
- RCRegistry CLI12%
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/sidebar.jsonUsage
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarInset,
SidebarItem,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar"
<SidebarProvider className="min-h-svh">
<Sidebar>
<SidebarContent>
<SidebarGroup label="Workspace">
<SidebarItem icon={<Inbox />} href="/inbox" badge={3} active>
Inbox
</SidebarItem>
<SidebarItem icon={<Folder />} href="/projects">Projects</SidebarItem>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset>
<SidebarTrigger />
…
</SidebarInset>
</SidebarProvider>Examples
Starts as a rail
defaultCollapsed opens on the 56px rail. Badges turn into a dot on the icon, and the hover label carries the count.
Settings navigation
collapsible="none" keeps the sidebar open on wide screens. Below md it is still a drawer.
API reference
SidebarProvider
Holds the state and renders a flex row: put Sidebar and SidebarInset inside. Give it min-h-svh for a page shell, or a fixed height for a framed panel.
| Prop | Type | Default |
|---|---|---|
collapsed | booleanRail state when controlled. | None |
defaultCollapsed | booleanInitial rail state when uncontrolled. | false |
onCollapsedChange | (collapsed: boolean) => voidCalled when the rail is toggled. | None |
shortcut | string⌘ or Ctrl plus this key toggles the sidebar. Ignored inside contenteditable. Pass false to turn it off. | "b" |
Sidebar
The panel. From md up it sits in the layout, sticky, as tall as the viewport or the provider, whichever is smaller. Below md it is an off-canvas drawer. It sets data-state="collapsed" | "expanded" and the group/sidebar class for styling your own content.
| Prop | Type | Default |
|---|---|---|
side | "left"Which edge it sits on. For the right, render it after SidebarInset. | "left" |
collapsible | "rail"Allow collapsing to the 56px icon rail. | "rail" |
color | NeonColorPalette name or hex. Falls back to --neon-accent, then cyan. | inherited |
SidebarHeader / SidebarFooter
Fixed rows at the top and bottom. They clip their content to the rail; fade your own text with useSidebar().collapsed, e.g. collapsed && "md:opacity-0".
| Prop | Type | Default |
|---|---|---|
className | stringClasses for the row. | None |
SidebarContent
The scrolling middle. Renders a nav labelled "Sidebar".
| Prop | Type | Default |
|---|---|---|
aria-label | stringName of the navigation landmark. | "Sidebar" |
SidebarGroup
A list of items with an optional heading. Items must live in a group, including in the footer.
| Prop | Type | Default |
|---|---|---|
label | ReactNodeHeading above the items. In the rail it becomes a short divider. | None |
SidebarItem
A row in a group. Renders a link with href, a button without, or your own element with asChild.
| Prop | Type | Default |
|---|---|---|
icon | ReactNode16px icon. It stays put while the sidebar collapses. | None |
href | stringRenders an a instead of a button. | None |
active | booleanMarks the current page: aria-current="page" and the lit tube. | false |
badge | ReactNodeCount or short tag at the end of the row. In the rail it becomes a dot on the icon. | None |
tooltip | ReactNodeText for the rail hover label. Defaults to the item's label. | None |
disabled | booleanDims the row and blocks clicks. | false |
onClick | (event) => voidBelow md, a click also closes the drawer unless you call event.preventDefault(). | None |
asChild | booleanMerge onto the single child, such as your router's Link. The child's children become the label. | false |
SidebarTrigger
An icon button that collapses or expands the sidebar, or opens the drawer below md. Children replace the icon.
| Prop | Type | Default |
|---|---|---|
color | NeonColorPalette name or hex for the focus ring. | inherited |
SidebarInset
The page next to the sidebar. A flex column that takes the remaining width. While the drawer is open it is inert.
| Prop | Type | Default |
|---|---|---|
className | stringClasses for the page column, e.g. overflow-y-auto. | None |
useSidebar()
Reads the nearest SidebarProvider. Throws outside one.
| Prop | Type | Default |
|---|---|---|
collapsed | booleanWhether the sidebar is on the rail (md and up). | None |
setCollapsed | (collapsed: boolean) => voidCollapse or expand. | None |
toggle | () => voidWhat the trigger and the shortcut call: the rail on desktop, the drawer below md. | None |
isMobile | booleanTrue below md, where the sidebar is a drawer. False during server rendering. | None |
openMobile | booleanWhether the drawer is open. | None |
setOpenMobile | (open: boolean) => voidOpen or close the drawer. | None |
Accessibility
- The trigger is a button named "Toggle sidebar" with aria-expanded, aria-controls pointing at the panel, and aria-keyshortcuts for ⌘B and Ctrl+B.
- The active item has aria-current="page". The tube is decorative and hidden from screen readers.
- On the rail the labels fade but stay in the accessibility tree, so every item keeps its name. The hover label is visual only and also appears on keyboard focus.
- The hover label closes on Escape, on scroll and when the pointer leaves the item.
- Below md the sidebar is a modal drawer: focus moves into it and Tab cycles inside it, Escape and the backdrop close it, the page behind is inert and stops scrolling, and focus returns to the trigger. The drawer renders into document.body, so no parent's z-index or transform can trap it.
- The width animation and the glide respect reduced motion.