shadimage
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 · light
iconslucide-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>
    )
}

More from hexui

Browse all components in this library →