hexui
blocksidebar-1source: ok · refreshed
Sidebar-1
A sidebar for a note-taking application.
npx shadcn add "https://hexui.sh/r/sidebar-1.json"free
Where it goes
sidebardashboard-shell
Use cases
dashboard
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lighticonslucide-react
Registry dependencies
buttondropdown-menusidebar
Files9 · source cached 10/8/2026, 7:42:01 AM
registry/blocks/sidebar/sidebar-1/source/app/sidebar/page.tsx
import { AppSidebar } from "../../components/app-sidebar"
import { SiteHeader } from "../../components/site-header"
import {
SidebarInset,
SidebarProvider,
} from "@/components/ui/sidebar"
export default function Page() {
return (
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<SiteHeader />
<div className="flex flex-1 flex-col p-4">
{/* Placeholder canvas for the page content preview. */}
<div className="min-h-screen flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
)
}
registry/blocks/sidebar/sidebar-1/source/components/app-sidebar.tsx
"use client"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
Sidebar,
SidebarContent,
SidebarHeader,
SidebarFooter,
} from "@/components/ui/sidebar"
import {
Globe,
Inbox,
House,
Search,
LibraryBig,
SquarePen,
Settings,
Store,
Trash2,
} from "lucide-react"
import { NavMain, type NavMainItem } from "./nav-main"
import { NavRecents, type NavRecentsItem } from "./nav-recents"
import { NavFavorites, type NavFavoritesItem } from "./nav-favorites"
import { NavPrivate, type NavPrivateItem } from "./nav-private"
import { NavSecondary, type NavSecondaryItem } from "./nav-secondary"
import { WorkSpaceSwitcher } from "./workspaces-switcher"
// Derive the workspace item type from the switcher's props so sample data stays in sync.
type WorkspaceItem = React.ComponentProps<typeof WorkSpaceSwitcher>["workspaces"][number]
// Sample data. `satisfies` validates each section against its nav component's item type.
const data = {
workspaces: [
{
name: "Acme Inc",
plan: "Enterprise",
logo: Globe,
},
{
name: "Nova Inc",
plan: "Enterprise",
logo: Globe,
},
] satisfies WorkspaceItem[],
navMain: [
{
title: "Search",
url: "#",
icon: Search,
},
{
title: "Home",
url: "#",
icon: House,
},
{
title: "Inbox",
url: "#",
icon: Inbox,
},
{
title: "Library",
url: "#",
icon: LibraryBig,
},
] satisfies NavMainItem[],
recents: [
{ title: "Quick Notes", url: "#", emoji: "📝" },
{ title: "Meeting Minutes", url: "#", emoji: "📋" },
{ title: "Ideas", url: "#", emoji: "💡" },
{ title: "Changelog", url: "#", emoji: "📜" },
] satisfies NavRecentsItem[],
private: [
{ title: "Health Tracker", url: "#", emoji: "💪" },
{ title: "Reading List", url: "#", emoji: "📚" },
{ title: "Passwords", url: "#", emoji: "🔐" },
{ title: "Shopping List", url: "#", emoji: "🛒" },
{ title: "Home Tasks", url: "#", emoji: "🏠" },
{ title: "Life Goals", url: "#", emoji: "🎯" },
{ title: "Side Projects", url: "#", emoji: "🛠️" },
{ title: "Ideas Vault", url: "#", emoji: "🏦" },
{ title: "Recipes", url: "#", emoji: "🍳" },
] satisfies NavPrivateItem[],
favorites: [
{ title: "Project Dashboard", url: "#", emoji: "📊" },
{ title: "Design System", url: "#", emoji: "🎨" },
{ title: "Component Library", url: "#", emoji: "🧱" },
{ title: "Development Guide", url: "#", emoji: "👨💻" },
{ title: "Team Wiki", url: "#", emoji: "👥" },
{ title: "Product Vision", url: "#", emoji: "🔭" },
{ title: "Launch Checklist", url: "#", emoji: "✅" },
{ title: "User Feedback", url: "#", emoji: "💬" },
{ title: "Analytics", url: "#", emoji: "📈" },
{ title: "Performance Metrics", url: "#", emoji: "⚡" },
{ title: "Security Policy", url: "#", emoji: "🛡️" },
{ title: "Database Schema", url: "#", emoji: "🗄️" },
{ title: "API Keys", url: "#", emoji: "🔑" },
{ title: "Release Notes", url: "#", emoji: "📣" },
{ title: "Support Docs", url: "#", emoji: "📚" },
] satisfies NavFavoritesItem[],
navSecondary: [
{
title: "Settings",
url: "#",
icon: Settings,
},
{
title: "Marketplace",
url: "#",
icon: Store,
},
{
title: "Trash",
url: "#",
icon: Trash2,
},
] satisfies NavSecondaryItem[],
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar {...props}>
<SidebarHeader className="flex flex-col">
<div className="flex flex-row w-full justify-between">
<WorkSpaceSwitcher workspaces={data.workspaces} />
<Button variant="ghost" size="icon" className="ml-auto" aria-label="New page">
<SquarePen />
</Button>
</div>
<NavMain items={data.navMain} />
</SidebarHeader>
<SidebarContent>
<NavRecents items={data.recents} />
<NavFavorites items={data.favorites} />
<NavPrivate items={data.private} />
</SidebarContent>
<SidebarFooter className="border-t">
<NavSecondary items={data.navSecondary} />
</SidebarFooter>
</Sidebar>
)
}
registry/blocks/sidebar/sidebar-1/source/components/nav-favorites.tsx
import {
MoreHorizontal,
type LucideIcon,
} from "lucide-react"
import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
export type NavFavoritesItem = {
title: string
url: string
isActive?: boolean
icon?: LucideIcon
emoji?: string
}
export function NavFavorites({ items }: { items: NavFavoritesItem[] }) {
return (
<SidebarGroup>
<SidebarGroupLabel className="gap-1.5">
Favorites
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((subItem) => (
<SidebarMenuItem key={subItem.title}>
<SidebarMenuButton isActive={subItem.isActive}>
<a className="flex items-center gap-2" href={subItem.url}>
{/* Render either a Lucide icon or an emoji glyph (mutually exclusive). */}
{subItem.icon ? <subItem.icon className="size-4" /> : null}
{subItem.emoji ? (
<span aria-hidden="true" className="text-[14px] leading-none">
{subItem.emoji}
</span>
) : null}
<span>{subItem.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70" aria-label="More favorites">
<MoreHorizontal aria-hidden="true" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}
registry/blocks/sidebar/sidebar-1/source/components/nav-main.tsx
import { type LucideIcon } from "lucide-react"
import {
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
export type NavMainItem = {
title: string
url: string
isActive?: boolean
icon?: LucideIcon
emoji?: string
}
export function NavMain({ items }: { items: NavMainItem[] }) {
return (
<SidebarGroup className="p-0">
<SidebarGroupContent>
<SidebarMenu>
{items.map((subItem) => (
<SidebarMenuItem key={subItem.title}>
<SidebarMenuButton isActive={subItem.isActive}>
<a className="flex items-center gap-2" href={subItem.url}>
{/* Render either a Lucide icon or an emoji glyph (mutually exclusive). */}
{subItem.icon ? <subItem.icon className="size-4" /> : null}
{subItem.emoji ? (
<span aria-hidden="true" className="text-[14px] leading-none">
{subItem.emoji}
</span>
) : null}
<span>{subItem.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}
registry/blocks/sidebar/sidebar-1/source/components/nav-private.tsx
import {
MoreHorizontal,
type LucideIcon,
} from "lucide-react"
import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
export type NavPrivateItem = {
title: string
url: string
isActive?: boolean
icon?: LucideIcon
emoji?: string
}
export function NavPrivate({ items }: { items: NavPrivateItem[] }) {
return (
<SidebarGroup>
<SidebarGroupLabel className="gap-1.5">
Private
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((subItem) => (
<SidebarMenuItem key={subItem.title}>
<SidebarMenuButton isActive={subItem.isActive}>
<a className="flex items-center gap-2" href={subItem.url}>
{/* Render either a Lucide icon or an emoji glyph (mutually exclusive). */}
{subItem.icon ? <subItem.icon className="size-4" /> : null}
{subItem.emoji ? (
<span aria-hidden="true" className="text-[14px] leading-none">
{subItem.emoji}
</span>
) : null}
<span>{subItem.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70" aria-label="More private pages">
<MoreHorizontal aria-hidden="true" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}
registry/blocks/sidebar/sidebar-1/source/components/nav-recents.tsx
import {
MoreHorizontal,
type LucideIcon,
} from "lucide-react"
import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
export type NavRecentsItem = {
title: string
url: string
isActive?: boolean
icon?: LucideIcon
emoji?: string
}
export function NavRecents({ items }: { items: NavRecentsItem[] }) {
return (
<SidebarGroup>
<SidebarGroupLabel className="gap-1.5">
Recents
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((subItem) => (
<SidebarMenuItem key={subItem.title}>
<SidebarMenuButton isActive={subItem.isActive}>
<a className="flex items-center gap-2" href={subItem.url}>
{/* Render either a Lucide icon or an emoji glyph (mutually exclusive). */}
{subItem.icon ? <subItem.icon className="size-4" /> : null}
{subItem.emoji ? (
<span aria-hidden="true" className="text-[14px] leading-none">
{subItem.emoji}
</span>
) : null}
<span>{subItem.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70" aria-label="More recents">
<MoreHorizontal aria-hidden="true" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}
registry/blocks/sidebar/sidebar-1/source/components/nav-secondary.tsx
import { type LucideIcon } from "lucide-react"
import {
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
export type NavSecondaryItem = {
title: string
url: string
isActive?: boolean
icon?: LucideIcon
emoji?: string
}
export function NavSecondary({ items }: { items: NavSecondaryItem[] }) {
return (
<SidebarGroup className="p-0 mt-auto">
<SidebarGroupContent>
<SidebarMenu>
{items.map((subItem) => (
<SidebarMenuItem key={subItem.title}>
<SidebarMenuButton isActive={subItem.isActive}>
<a className="flex items-center gap-2" href={subItem.url}>
{/* Render either a Lucide icon or an emoji glyph (mutually exclusive). */}
{subItem.icon ? <subItem.icon className="size-4" /> : null}
{subItem.emoji ? (
<span aria-hidden="true" className="text-[14px] leading-none">
{subItem.emoji}
</span>
) : null}
<span>{subItem.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}
registry/blocks/sidebar/sidebar-1/source/components/site-header.tsx
import { SidebarTrigger } from "@/components/ui/sidebar"
export function SiteHeader() {
return (
<header className="flex h-12 shrink-0 items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" />
<div className="flex mx-1 h-4.5 w-0.5 bg-neutral-200 dark:bg-neutral-900" />
Quick Notes
</header>
)
}registry/blocks/sidebar/sidebar-1/source/components/workspaces-switcher.tsx
"use client"
import * as React from "react"
import { ChevronDown, Plus, type LucideIcon } from "lucide-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
export function WorkSpaceSwitcher({
workspaces,
}: {
workspaces: {
name: string
logo: LucideIcon
plan: string
}[]
}) {
// Default to the first workspace; guard against an empty `workspaces` array.
const [activeWorkspace, setActiveWorkspace] = React.useState(workspaces[0])
if (!activeWorkspace) {
return null
}
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger
// `render` composes the trigger with a SidebarMenuButton so it adopts sidebar styling.
render={<SidebarMenuButton className="w-fit px-1.5" />}
>
<div className="flex aspect-square size-5 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground">
<activeWorkspace.logo className="size-3" />
</div>
<span className="truncate font-medium">{activeWorkspace.name}</span>
<ChevronDown className="opacity-50" />
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-64 rounded-lg"
align="start"
side="bottom"
sideOffset={4}
>
<DropdownMenuGroup>
<DropdownMenuLabel className="text-xs text-muted-foreground">
Workspaces
</DropdownMenuLabel>
{workspaces.map((workspace, index) => (
<DropdownMenuItem
key={workspace.name}
onClick={() => setActiveWorkspace(workspace)}
className="gap-2 p-2"
>
<div className="flex size-6 items-center justify-center rounded-xs border">
<workspace.logo className="size-4 shrink-0" />
</div>
{workspace.name}
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
</DropdownMenuItem>
))}
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="flex size-6 items-center justify-center rounded-md border bg-background">
<Plus className="size-4" />
</div>
<div className="font-medium text-muted-foreground">Add workspace</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
)
}