App Sidebar
A floating app sidebar: search that filters the nav, badges, nested groups, a sliding marker and an icon rail.
Overview
The whole left side of an app in one component. A workspace header, a search row that turns into a field (⌘K) and filters every item in place with arrow-key results, sections with headings, counts and unread dots, groups that fold open, a footer with settings and help, and a user row that opens an account menu.
The current page is marked by a raised pill that slides between rows rather than jumping. Collapse it (button or [) and the panel narrows to a 60px rail: icons stay exactly where they were while labels, badges and headings blur away, and hovering or focusing an icon shows its name in a tooltip. In the rail, the logo turns into the expand button on hover.
Navigation is links when you pass href, so middle-click and ⌘-click still open new tabs; pass onNavigate to route in-app. It can be controlled or left to manage its own state.
When to use
- The main navigation of a dashboard or SaaS app.
- Apps with enough pages that search and collapsible groups help.
When not to use
- A marketing site header: use a top nav.
- A short list of tabs within one page: use Tabs.
Installation
pnpm dlx shadcn@latest add https://sundaeui.dev/r/app-sidebar.jsonnpx shadcn@latest add https://sundaeui.dev/r/app-sidebar.jsonyarn dlx shadcn@latest add https://sundaeui.dev/r/app-sidebar.jsonbunx --bun shadcn@latest add https://sundaeui.dev/r/app-sidebar.jsonWorks in any React project. Files are written to components/sundae/. Read the source in the stage’s Code view. Pro assets need a personal token. See authenticated installs.
- No dependencies beyond React 18 or later.
- Copy app-sidebar.tsx and app-sidebar.css into components/sundae/.
- The CSS file is imported by the component, so no extra configuration is needed.
- Import the component and use it as shown below.
Usage
import { AppSidebar } from '@/components/sundae/app-sidebar'
<div style={{ display: 'flex', height: '100dvh', padding: 12, gap: 12 }}>
<AppSidebar
workspace={{ name: 'Northwind', plan: 'Team plan' }}
sections={[
{ id: 'main', items: [
{ id: 'home', label: 'Home', icon: 'home', href: '/' },
{ id: 'inbox', label: 'Inbox', icon: 'inbox', href: '/inbox', badge: 12 },
] },
{ id: 'work', label: 'Workspace', items: [
{ id: 'projects', label: 'Projects', icon: 'folder', children: [
{ id: 'web', label: 'Website relaunch', href: '/p/web' },
] },
] },
]}
footer={[{ id: 'settings', label: 'Settings', icon: 'settings', href: '/settings' }]}
user={{ name: 'Ada Lovelace', email: 'ada@northwind.co' }}
activeId={current}
onNavigate={(item) => router.push(item.href!)}
onUserAction={(id) => id === 'sign-out' && signOut()}
/>
<main style={{ flex: 1 }}>{children}</main>
</div>Props
| Prop | Type | Default |
|---|---|---|
workspace*Header. Initials come from the name unless given. | { name, plan?, initials?, color? } | — |
sections*{ id, label?, items }. Items are { id, label, icon?, href?, badge?, children? }. | SidebarSection[] | — |
footerRows pinned above the user, e.g. Settings and Help. | SidebarItem[] | — |
user / userActionsAccount row and its menu. Defaults to Profile, Settings, Sign out. | { name, email?, initials? } / { id, label, danger? }[] | — |
activeIdThe current page. Its group opens and the marker slides to it. | string | — |
onNavigateCalled on click or Enter in search. Without it, rows with href are plain links. | (item) => void | — |
onUserActionCalled with the chosen account action. | (id: string) => void | — |
notice / onDismissNoticeA dismissible card above the footer; hidden in the rail. | { id, title, body?, action? } | — |
collapsed / defaultCollapsed / onCollapsedChangeControlled or uncontrolled rail state. | boolean | false |
searchShortcutOpens search with ⌘ or Ctrl. null turns it off. | string | null | 'k' |
collapseShortcutToggles the rail when no field has focus. | string | null | '[' |
icon16 built-in icons (home, inbox, folder, chart, …) or your own 16px node. | SidebarIconName | ReactNode | — |
themeauto follows a .dark ancestor. | 'auto' | 'light' | 'dark' | 'auto' |
Accessibility
- Each section is a labelled nav; the current row has aria-current="page".
- Groups are buttons with aria-expanded and aria-controls; closed groups are inert.
- Search is a combobox: arrow keys move through results (aria-activedescendant), Enter opens, Escape closes and returns focus.
- In the rail every row keeps an accessible name, and tooltips appear on keyboard focus as well as hover.
- The account menu uses role="menu": focus moves into it, arrows/Home/End move, Escape closes and returns focus.
- Reduced motion removes the slide, blur and fold animations.
Customization
- Custom properties on .sd-as: --as-w and --as-rail (widths), --as-accent (badges, focus, unread), --as-bg, --as-active, --as-logo.
FAQ
- Is App Sidebar free?
- App Sidebar is part of Sundae Pro. The live preview is open to everyone; the source installs with a Pro account.
- How do I install App Sidebar?
- Run npx shadcn@latest add https://sundaeui.dev/r/app-sidebar.json. The files are written to components/sundae/. It has no dependencies beyond React 18 or later.
- Does App Sidebar support keyboard and screen readers?
- Each section is a labelled nav; the current row has aria-current="page". Groups are buttons with aria-expanded and aria-controls; closed groups are inert.
- Does App Sidebar respect reduced motion?
- Reduced motion removes the slide, blur and fold animations.
More like this
All navigation →- Pro
Collapsible Sidebar
Navigation · Refined - Pro
Command Palette
Navigation · Refined - Pro
File Tree
Navigation · Refined Vertical Tabs
Navigation · Refined- Pro
Nav Dropdown
Navigation · Refined Bottom Tab Bar
Navigation · Refined