Sidebar 1
App navigation with groups, nested items and a collapsed icon rail. This website uses it.
View code
"use client";
import {
Activity,
Blocks,
Calendar,
CreditCard,
FolderKanban,
Globe,
Inbox,
LayoutGrid,
LogOut,
Settings,
Terminal,
Users,
} from "lucide-react";
import { useState } from "react";
import {
Sidebar1,
Sidebar1Brand,
Sidebar1Collapsible,
Sidebar1Content,
Sidebar1Footer,
Sidebar1Group,
Sidebar1Header,
Sidebar1Item,
Sidebar1Search,
Sidebar1Toggle,
} from "@/components/kit/sidebar-1";
export function Sidebar1Demo() {
const [collapsed, setCollapsed] = useState(false);
const [page, setPage] = useState("Home");
const item = (name: string, icon: React.ReactNode, badge?: number) => (
<Sidebar1Item icon={icon} badge={badge} active={page === name} onClick={() => setPage(name)}>
{name}
</Sidebar1Item>
);
const subItem = (name: string) => (
<Sidebar1Item active={page === name} onClick={() => setPage(name)}>
{name}
</Sidebar1Item>
);
return (
<div className="flex h-180 w-full bg-background text-foreground">
<Sidebar1 collapsed={collapsed}>
<Sidebar1Header>
<Sidebar1Brand logo="A" title="Acme Corp" subtitle="Pro Plan" onClick={() => setPage("Home")} />
</Sidebar1Header>
<Sidebar1Content aria-label="Main">
<Sidebar1Search />
{item("Home", <LayoutGrid />)}
{item("Inbox", <Inbox />, 12)}
{item("Analytics", <Activity />)}
<Sidebar1Group label="Workspace">
<Sidebar1Collapsible icon={<FolderKanban />} label="Projects">
{subItem("Active")}
{subItem("Archived")}
</Sidebar1Collapsible>
{item("Calendar", <Calendar />)}
<Sidebar1Collapsible icon={<Users />} label="Team">
{subItem("Members")}
{subItem("Roles")}
</Sidebar1Collapsible>
<Sidebar1Collapsible icon={<Globe />} label="Customers">
{subItem("Accounts")}
{subItem("Invoices")}
</Sidebar1Collapsible>
{item("Finance", <CreditCard />)}
</Sidebar1Group>
<Sidebar1Group label="Developers">
{item("API Keys", <Terminal />)}
{item("Webhooks", <Blocks />)}
</Sidebar1Group>
</Sidebar1Content>
<Sidebar1Footer>
{item("Settings", <Settings />)}
<Sidebar1Item icon={<LogOut />}>Log out</Sidebar1Item>
</Sidebar1Footer>
</Sidebar1>
<div className="flex min-w-0 flex-1 flex-col max-sm:hidden">
<header className="flex h-14 shrink-0 items-center gap-3 border-b px-3">
<Sidebar1Toggle collapsed={collapsed} onClick={() => setCollapsed((value) => !value)} />
<span className="truncate text-sm text-muted-foreground">
Acme Corp <span className="mx-1">/</span> <span className="font-medium text-foreground">{page}</span>
</span>
</header>
<div className="flex flex-1 flex-col gap-5 overflow-hidden p-6" aria-hidden="true">
<div className="h-8 w-48 rounded-lg bg-muted" />
<div className="grid h-32 shrink-0 grid-cols-2 gap-5">
<div className="rounded-xl border bg-card" />
<div className="rounded-xl border bg-card" />
</div>
<div className="flex flex-1 flex-col gap-3 rounded-xl border bg-card p-5">
<div className="h-5 w-40 rounded-md bg-muted" />
<div className="h-11 rounded-lg bg-muted" />
<div className="h-11 rounded-lg bg-muted" />
<div className="h-11 rounded-lg bg-muted" />
</div>
</div>
</div>
</div>
);
}
Build your navigation from small parts. You hold the collapsed state, so you can save it or change it from anywhere.
Installation
bunx --bun shadcn@latest add HarshitMahajan/UI/sidebar-1Copies the code to components/kit/sidebar-1.tsx along with button, which it uses, installs its packages, and adds the tokens it needs to your globals.css. Your existing shadcn components aren't touched.
Usage
import {
Sidebar1,
Sidebar1Brand,
Sidebar1Collapsible,
Sidebar1Content,
Sidebar1Footer,
Sidebar1Group,
Sidebar1Header,
Sidebar1Item,
Sidebar1Search,
Sidebar1Toggle,
} from "@/components/kit/sidebar-1"<Sidebar1 collapsed={collapsed}>
<Sidebar1Header>
<Sidebar1Brand logo="A" title="Acme Corp" subtitle="Pro Plan" />
</Sidebar1Header>
<Sidebar1Content aria-label="Main">
<Sidebar1Search />
<Sidebar1Item icon={<LayoutGrid />} active>Home</Sidebar1Item>
<Sidebar1Group label="Workspace">
<Sidebar1Collapsible icon={<Users />} label="Team">
<Sidebar1Item>Members</Sidebar1Item>
</Sidebar1Collapsible>
</Sidebar1Group>
</Sidebar1Content>
<Sidebar1Footer>
<Sidebar1Item icon={<Settings />}>Settings</Sidebar1Item>
</Sidebar1Footer>
</Sidebar1>Composition
Sidebar1
├── Sidebar1Header
│ └── Sidebar1Brand
├── Sidebar1Content
│ ├── Sidebar1Search
│ ├── Sidebar1Item
│ └── Sidebar1Group
│ ├── Sidebar1Item
│ └── Sidebar1Collapsible
│ └── Sidebar1Item
└── Sidebar1Footer
└── Sidebar1Item
Sidebar1Toggle (usually in your page header)Features
- Collapses to a 60px icon rail over 200ms
- Groups, nested rows and count badges
- Items render as buttons or as links through the render prop
- Nested rows use Base UI Collapsible, and search uses Base UI Input
Icon rail
Set collapsed to show only icons. Give each item an aria-label, since its text is out of view.
View code
import { Activity, Inbox, LayoutGrid, Settings } from "lucide-react";
import { Sidebar1, Sidebar1Brand, Sidebar1Content, Sidebar1Footer, Sidebar1Header, Sidebar1Item } from "@/components/kit/sidebar-1";
export function Sidebar1Rail() {
return (
<div className="flex h-80 w-full bg-background text-foreground">
<Sidebar1 collapsed>
<Sidebar1Header>
<Sidebar1Brand logo="A" title="Acme Corp" />
</Sidebar1Header>
<Sidebar1Content aria-label="Main">
<Sidebar1Item icon={<LayoutGrid />} active aria-label="Home">
Home
</Sidebar1Item>
<Sidebar1Item icon={<Inbox />} badge={12} aria-label="Inbox, 12 new">
Inbox
</Sidebar1Item>
<Sidebar1Item icon={<Activity />} aria-label="Analytics">
Analytics
</Sidebar1Item>
</Sidebar1Content>
<Sidebar1Footer>
<Sidebar1Item icon={<Settings />} aria-label="Settings">
Settings
</Sidebar1Item>
</Sidebar1Footer>
</Sidebar1>
</div>
);
}
Nested items
Use Sidebar1Collapsible for sections with sub-items, and defaultOpen to start one open.
View code
import { FolderKanban, Users } from "lucide-react";
import { Sidebar1, Sidebar1Collapsible, Sidebar1Content, Sidebar1Item } from "@/components/kit/sidebar-1";
export function Sidebar1Nested() {
return (
<div className="flex h-80 w-full bg-background text-foreground">
<Sidebar1>
<Sidebar1Content aria-label="Main">
<Sidebar1Collapsible icon={<FolderKanban />} label="Projects" defaultOpen>
<Sidebar1Item active>Active</Sidebar1Item>
<Sidebar1Item>Archived</Sidebar1Item>
</Sidebar1Collapsible>
<Sidebar1Collapsible icon={<Users />} label="Team">
<Sidebar1Item>Members</Sidebar1Item>
<Sidebar1Item>Roles</Sidebar1Item>
</Sidebar1Collapsible>
</Sidebar1Content>
</Sidebar1>
</div>
);
}
With links
Pass render to make items real links. In Next.js, pass your Link component.
View code
import { Inbox, LayoutGrid } from "lucide-react";
import { Sidebar1, Sidebar1Content, Sidebar1Group, Sidebar1Item } from "@/components/kit/sidebar-1";
// In a Next.js app, pass render={<Link href="/inbox" />} to get client-side navigation.
export function Sidebar1Links() {
return (
<div className="flex h-60 w-full bg-background text-foreground">
<Sidebar1>
<Sidebar1Content aria-label="Main">
<Sidebar1Group label="Pages" className="mt-0">
<Sidebar1Item render={<a href="#install" />} icon={<LayoutGrid />} active>
Home
</Sidebar1Item>
<Sidebar1Item render={<a href="#install" />} icon={<Inbox />} badge={3}>
Inbox
</Sidebar1Item>
</Sidebar1Group>
</Sidebar1Content>
</Sidebar1>
</div>
);
}
Accessibility
Name the navigation
Sidebar1Content renders a nav element. Give it an aria-label so people can tell navigation areas apart.
The current page
Set active on the current item. It's marked with aria-current, so screen readers announce it too.
API Reference
Sidebar1
The frame.
| Prop | Type | Default | Description |
|---|---|---|---|
| collapsed | boolean | false | Switches to the icon rail. |
| className | string | - | Additional classes for the frame. |
Sidebar1Toggle
The collapse button. Accepts all Button props.
| Prop | Type | Default | Description |
|---|---|---|---|
| collapsed | boolean | - | The current state, which sets its icon and label. |
Sidebar1Brand
Your logo and name.
| Prop | Type | Default | Description |
|---|---|---|---|
| logo | ReactNode | - | A letter or an image. |
| title | string | - | The name. |
| subtitle | string | - | A line under the name. |
| onClick | () => void | - | Makes it a button with a chevron, such as a workspace switcher. |
Sidebar1Item
One row.
| Prop | Type | Default | Description |
|---|---|---|---|
| icon | ReactNode | - | The row's icon. |
| active | boolean | false | Marks the current page. |
| badge | ReactNode | - | A short count on the right. Hidden on the rail. |
| render | ReactElement | function | - | Render the row as another element, such as a link or your router's Link. |
Sidebar1Collapsible
A row that opens to show sub-items. It closes on the rail.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | - | The row's text. |
| icon | ReactNode | - | The row's icon. |
| defaultOpen | boolean | false | Whether it starts open. |
Sidebar1Group
A labeled set of rows.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | - | The group's name. It fades on the rail. |
Sidebar1Search
A filter field styled as a row. Accepts all Base UI Input props.
Sidebar1Header
The top area.
Sidebar1Content
The scrolling middle. Renders a nav element.
Sidebar1Footer
The bottom area, above a line.