Sidebar
A full-height source list sidebar with sections, symbols and selection.
@cupertinoui-pro/mac-sidebarCupertinoUI Pro"use client"
import * as React from "react"
import {
AppWindowIcon,
ClockIcon,
CloudIcon,
DownloadIcon,
FileIcon,
MonitorIcon,
} from "lucide-react"
import {
MacSidebar,
MacSidebarContent,
MacSidebarHeader,
MacSidebarItem,
MacSidebarItemBadge,
MacSidebarSection,
MacSidebarSectionLabel,
} from "@/components/ui/mac-sidebar"
import { Toolbar, ToolbarTitle } from "@/components/ui/toolbar"
import {
Window,
WindowContent,
WindowControls,
WindowMain,
} from "@/components/ui/window"
const favorites = [
{ label: "Recents", icon: ClockIcon },
{ label: "Applications", icon: AppWindowIcon },
{ label: "Desktop", icon: MonitorIcon },
{ label: "Documents", icon: FileIcon },
{ label: "Downloads", icon: DownloadIcon, count: 3 },
]
export default function MacSidebarDemo() {
const [selected, setSelected] = React.useState("Documents")
return (
<Window className="h-80 w-full max-w-xl">
<MacSidebar className="w-52">
<MacSidebarHeader>
<WindowControls />
</MacSidebarHeader>
<MacSidebarContent>
<MacSidebarSection>
<MacSidebarSectionLabel className="h-6">Favorites</MacSidebarSectionLabel>
{favorites.map((item) => (
<MacSidebarItem
key={item.label}
active={selected === item.label}
onClick={() => setSelected(item.label)}
>
<item.icon strokeWidth={1.6} />
{item.label}
{item.count && <MacSidebarItemBadge>{item.count}</MacSidebarItemBadge>}
</MacSidebarItem>
))}
</MacSidebarSection>
<MacSidebarSection>
<MacSidebarSectionLabel>iCloud</MacSidebarSectionLabel>
<MacSidebarItem
active={selected === "iCloud Drive"}
onClick={() => setSelected("iCloud Drive")}
>
<CloudIcon strokeWidth={1.6} />
iCloud Drive
</MacSidebarItem>
</MacSidebarSection>
</MacSidebarContent>
</MacSidebar>
<WindowMain>
<Toolbar>
<ToolbarTitle title={selected} />
</Toolbar>
<WindowContent className="flex items-center justify-center bg-mac-content text-mac-label-secondary">
{selected}
</WindowContent>
</WindowMain>
</Window>
)
}Installation
pnpm dlx shadcn@latest add @cupertinoui-pro/mac-sidebarPart of CupertinoUI Pro: the CLI installs it with your license key, once the @cupertinoui-pro registry is in components.json.
Usage
import {
MacSidebar,
MacSidebarContent,
MacSidebarEmpty,
MacSidebarHeader,
MacSidebarItem,
MacSidebarSearch,
MacSidebarSection,
MacSidebarSectionLabel,
} from "@/components/ui/mac-sidebar"
<MacSidebar>
<MacSidebarHeader>
<WindowControls />
</MacSidebarHeader>
<MacSidebarSearch />
<MacSidebarContent>
<MacSidebarSection>
<MacSidebarSectionLabel>Favorites</MacSidebarSectionLabel>
<MacSidebarItem active>
<ClockIcon /> Recents
</MacSidebarItem>
</MacSidebarSection>
<MacSidebarEmpty />
</MacSidebarContent>
</MacSidebar>Examples
Search
Add a MacSidebarSearch and the items filter as you type. Sections with no matching items hide with their label, and MacSidebarEmpty shows when nothing matches. Escape clears the search; the down arrow moves to the first result.
"use client"
import * as React from "react"
import {
ArchiveIcon,
FileIcon,
FlagIcon,
FolderIcon,
InboxIcon,
SendIcon,
Trash2Icon,
} from "lucide-react"
import {
MacSidebar,
MacSidebarContent,
MacSidebarEmpty,
MacSidebarHeader,
MacSidebarItem,
MacSidebarItemBadge,
MacSidebarSearch,
MacSidebarSection,
MacSidebarSectionLabel,
} from "@/components/ui/mac-sidebar"
import { Toolbar, ToolbarTitle } from "@/components/ui/toolbar"
import {
Window,
WindowContent,
WindowControls,
WindowMain,
} from "@/components/ui/window"
const mailboxes = [
{ label: "Inbox", icon: InboxIcon, count: 4 },
{ label: "Flagged", icon: FlagIcon, count: 2 },
{ label: "Drafts", icon: FileIcon },
{ label: "Sent", icon: SendIcon },
{ label: "Archive", icon: ArchiveIcon },
{ label: "Trash", icon: Trash2Icon, keywords: ["deleted", "bin"] },
]
const folders = ["Projects", "Receipts", "Travel", "Newsletters"]
// Type in the search field to filter the items. Sections without a match
// disappear, and keywords let an item match words that aren't in its label.
export default function MacSidebarSearchDemo() {
const [selected, setSelected] = React.useState("Inbox")
return (
<Window className="h-96 w-full max-w-xl">
<MacSidebar className="w-56">
<MacSidebarHeader>
<WindowControls />
</MacSidebarHeader>
<MacSidebarSearch placeholder="Search mailboxes" />
<MacSidebarContent>
<MacSidebarSection>
<MacSidebarSectionLabel className="h-6">Favorites</MacSidebarSectionLabel>
{mailboxes.map((item) => (
<MacSidebarItem
key={item.label}
keywords={item.keywords}
active={selected === item.label}
onClick={() => setSelected(item.label)}
>
<item.icon strokeWidth={1.6} />
{item.label}
{item.count && <MacSidebarItemBadge>{item.count}</MacSidebarItemBadge>}
</MacSidebarItem>
))}
</MacSidebarSection>
<MacSidebarSection>
<MacSidebarSectionLabel>On My Mac</MacSidebarSectionLabel>
{folders.map((label) => (
<MacSidebarItem
key={label}
active={selected === label}
onClick={() => setSelected(label)}
>
<FolderIcon strokeWidth={1.6} />
{label}
</MacSidebarItem>
))}
</MacSidebarSection>
<MacSidebarEmpty />
</MacSidebarContent>
</MacSidebar>
<WindowMain>
<Toolbar>
<ToolbarTitle title={selected} className="pl-3" />
</Toolbar>
<WindowContent className="flex items-center justify-center bg-mac-content text-mac-label-secondary">
{selected}
</WindowContent>
</WindowMain>
</Window>
)
}API Reference
Every part forwards the props of the underlying element. The props below are the ones CupertinoUI adds or changes.
MacSidebar
A full-height, translucent source list. It holds the search text for the items inside it.
| Prop | Type | Description |
|---|---|---|
defaultSearchValue | stringDefault: "" | The initial search text. |
searchValue | string | The search text, when you control it. |
onSearchValueChange | (value: string) => void | Called when the search text changes. |
filter | (text: string, query: string) => boolean | Decides whether an item matches. The default ignores case and accents. |
className | string | Extra classes, merged with the component's own. |
MacSidebarHeader
The 52pt strip level with the toolbar, for the window controls.
| Prop | Type | Description |
|---|---|---|
className | string | Extra classes, merged with the component's own. |
MacSidebarSearch
A filled search field wired to the sidebar. Accepts the props of SearchField.
| Prop | Type | Description |
|---|---|---|
placeholder | stringDefault: "Search" | The placeholder text. |
className | string | Extra classes, merged with the component's own. |
MacSidebarItem
| Prop | Type | Description |
|---|---|---|
active | booleanDefault: false | Marks the selected item. |
value | string | Text to match instead of the item's own text. |
keywords | string[] | More terms the item matches, e.g. the settings inside a pane. |
render | ReactElement | Render a link or another element with the item's styles. |
className | string | Extra classes, merged with the component's own. |
MacSidebarItemBadge
Trailing content: a count or a small button.
| Prop | Type | Description |
|---|---|---|
className | string | Extra classes, merged with the component's own. |
MacSidebarEmpty
Shown in place of the items when the search matches nothing.
| Prop | Type | Description |
|---|---|---|
children | ReactNodeDefault: "No Results" | The message. |
className | string | Extra classes, merged with the component's own. |