components

Sidebar

A sidebar menu that collapses, works on mobile, and supports themes.

Source Code
Files
components/app-sidebar.tsx
"use client"

import React, { ComponentType } from "react"
import {
	Box,
	Calendar,
	ChevronDown,
	ChevronRight,
	ClipboardList,
	FileChartColumn,
	Headset,
	Inbox,
	Search,
	Settings,
	TvMinimal,
	Users2,
} from "lucide-react"
import { Badge } from "@/registry/ui/badge"
import { IconButton } from "@/registry/ui/button"
import {
	Dropdown,
	DropdownContent,
	DropdownItem,
	DropdownLabel,
	DropdownTrigger,
} from "@/registry/ui/dropdown"
import {
	HoverCard,
	HoverCardContent,
	HoverCardTrigger,
} from "@/registry/ui/hover-card"
import { Input, InputWrapper } from "@/registry/ui/input"
import {
	Sidebar,
	SidebarCollapsible,
	SidebarCollapsibleContent,
	SidebarCollapsibleTrigger,
	SidebarContent,
	SidebarFooter,
	SidebarGroup,
	SidebarGroupContent,
	SidebarGroupLabel,
	SidebarHeader,
	SidebarMenu,
	SidebarMenuBadge,
	SidebarMenuButton,
	SidebarMenuItem,
	SidebarMenuSub,
	SidebarMenuSubItem,
	SidebarSeparator,
	SidebarTrigger,
	useSidebar,
} from "@/registry/ui/sidebar"
import { InfoCard } from "./info-card"
import {
	AcmeLogo,
	CircleLogo,
	DiscordLogo,
	DriveLogo,
	Logo,
	MageLogo,
	NotionLogo,
	RadianCoreLogo,
} from "./logos"
import { SidebarFooterUser } from "./sidebar-footer-user"

interface SubItem {
	label: string
	href: string
	icon: ComponentType<{ className?: string }>
}

interface NavItem {
	label: string
	icon: ComponentType<{ className?: string }>
	href?: string
	subitems?: SubItem[]
	isActive?: boolean
	badge?: React.ReactNode
}

interface NavGroup {
	title: string | null
	items: NavItem[]
}

const mainData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Home",
				icon: TvMinimal,
				href: "#",
			},
			{
				label: "Inbox",
				icon: Inbox,
				href: "#",
				isActive: true,
				badge: 4,
			},
			{
				label: "Calendar",
				icon: Calendar,
				href: "#",
			},
			{
				label: "Analytics",
				icon: FileChartColumn,
				href: "#",
			},
		],
	},
	{
		title: "Extension",
		items: [
			{
				label: "Subscribers",
				icon: Users2,
				href: "#",
			},
			{
				label: "Reports",
				icon: ClipboardList,
				href: "#",
			},
			{
				label: "Integrations",
				icon: Box,
				subitems: [
					{
						label: "Notion",
						icon: NotionLogo,
						href: "#",
					},
					{
						label: "Google Drive",
						icon: DriveLogo,
						href: "#",
					},
					{
						label: "Discord",
						icon: DiscordLogo,
						href: "#",
					},
				],
			},
		],
	},
	{
		title: "Projects",
		items: [
			{
				icon: MageLogo,
				label: "Mage Icons",
				href: "#",
			},
			{
				icon: AcmeLogo,
				label: "Acme Inc",
				href: "#",
			},
			{
				icon: RadianCoreLogo,
				label: "Radian Core",
				href: "#",
			},
		],
	},
]

const footerData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Help Center",
				icon: Headset,
				href: "#",
			},
			{
				label: "Settings",
				icon: Settings,
				href: "#",
			},
		],
	},
]

export function AppSidebar() {
	const { setOpen, state, isMobile } = useSidebar()
	const inputRef = React.useRef<HTMLInputElement>(null)
	// For opening dropdown on hover
	const [openItem, setOpenItem] = React.useState<string | null>(null)
	const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)
	const [hoverOpen, setHoverOpen] = React.useState(false)

	const openMenu = (title: string) => {
		if (timeoutRef.current) clearTimeout(timeoutRef.current)
		setOpenItem(title)
	}

	const closeMenu = () => {
		timeoutRef.current = setTimeout(() => {
			setOpenItem(null)
		}, 150)
	}

	return (
		<Sidebar collapsible="icon">
			<SidebarHeader className="p-0">
				<div className="group/header relative flex items-center gap-2 px-2.5 pb-2 pt-4 group-data-[state=expanded]:pl-5 group-data-[state=expanded]:pr-3">
					<div className="z-0 group-data-[state=collapsed]:px-2 group-data-[state=collapsed]:py-1 group-hover/header:group-data-[state=collapsed]:opacity-0">
						<Logo />
					</div>
					<span className="truncate font-semibold group-data-[collapsible=icon]:hidden">
						Debcon
					</span>
					<SidebarTrigger
						size="32"
						className="group-hover/header:opacity-100! z-10 ml-auto group-data-[collapsible=icon]:absolute group-data-[collapsible=icon]:left-4 group-data-[collapsible=icon]:top-4 group-data-[collapsible=icon]:ml-0 group-data-[state=collapsed]:opacity-0"
					/>
				</div>

				<div className="w-full px-3 py-2">
					<InputWrapper
						className="group-data-[state=collapsed]:hidden"
						size="36">
						<Search className="text-fg-tertiary" />
						<Input ref={inputRef} type="search" placeholder="Search" />
						<Badge size="20" color="neutral" variant="outline">
							⌘ /
						</Badge>
					</InputWrapper>

					<IconButton
						onClick={() => {
							setOpen(true)
							setTimeout(() => {
								inputRef.current?.focus()
							}, 200)
						}}
						size="36"
						variant="outline"
						color="neutral"
						className="group-data-[mobile=true]:hidden group-data-[state=expanded]:hidden">
						<Search className="text-fg-tertiary" />
					</IconButton>
				</div>
			</SidebarHeader>
			<SidebarContent>
				{mainData.map((section, idx) => (
					<SidebarGroup key={idx}>
						{section.title && (
							<SidebarGroupLabel className="uppercase">
								{section.title}
							</SidebarGroupLabel>
						)}
						<SidebarMenu>
							{section.items.map((item) => {
								if (!item.subitems) {
									return (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton
												size="32"
												isActive={item.isActive}
												tooltip={item.label}
												asChild>
												<a href={item.href}>
													{item.icon && <item.icon className="size-5" />}
													<span>{item.label}</span>
													{item.badge && (
														<SidebarMenuBadge
															variant="outline"
															color="neutral"
															className="bg-bg!">
															{item.badge}
														</SidebarMenuBadge>
													)}
												</a>
											</SidebarMenuButton>
										</SidebarMenuItem>
									)
								}

								if (state === "collapsed" && !isMobile) {
									return (
										<Dropdown
											open={openItem === item.label}
											onOpenChange={() => {}}
											modal={false}
											key={item.label}>
											<DropdownTrigger className="group/trigger w-full" asChild>
												<SidebarMenuButton
													onMouseEnter={() => openMenu(item.label)}
													onMouseLeave={closeMenu}
													onPointerDown={(e) => e.preventDefault()}>
													{item.icon && <item.icon />}
												</SidebarMenuButton>
											</DropdownTrigger>
											<DropdownContent
												onMouseEnter={() => openMenu(item.label)}
												onMouseLeave={closeMenu}
												side="right"
												className="w-60"
												align="center">
												{item.label && (
													<DropdownLabel>{item.label}</DropdownLabel>
												)}

												{item.subitems.map((subitem) => (
													<DropdownItem
														key={subitem.label}
														className="[&_svg]:size-5!"
														asChild>
														<a href={subitem.href}>
															<subitem.icon />
															{subitem.label}
														</a>
													</DropdownItem>
												))}
											</DropdownContent>
										</Dropdown>
									)
								}

								return (
									<SidebarCollapsible key={item.label}>
										<SidebarMenuItem>
											<SidebarCollapsibleTrigger className="w-full" asChild>
												<SidebarMenuButton tooltip={item.label}>
													{item.icon && <item.icon />}
													<span>{item.label}</span>
													<ChevronDown className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180" />
												</SidebarMenuButton>
											</SidebarCollapsibleTrigger>
											<SidebarCollapsibleContent>
												<SidebarMenuSub>
													{item.subitems.map((subitem) => (
														<SidebarMenuSubItem key={subitem.label}>
															<SidebarMenuButton asChild>
																<a href={subitem.href}>
																	<subitem.icon />
																	{subitem.label}
																</a>
															</SidebarMenuButton>
														</SidebarMenuSubItem>
													))}
												</SidebarMenuSub>
											</SidebarCollapsibleContent>
										</SidebarMenuItem>
									</SidebarCollapsible>
								)
							})}
						</SidebarMenu>
					</SidebarGroup>
				))}

				<div className="mt-auto p-2 px-3 pb-1.5 group-data-[state=collapsed]:px-3.5">
					<HoverCard
						open={state === "expanded" ? false : hoverOpen}
						onOpenChange={setHoverOpen}>
						<HoverCardTrigger asChild>
							<SidebarMenuButton
								className="bg-elevation-level2! h-auto border px-2.5 py-1.5 group-data-[state=expanded]:cursor-default"
								asChild>
								<div>
									<CircleLogo />
									<div className="flex flex-col">
										<span className="text-[13px] font-medium leading-5">
											Version 1.2 Update
										</span>
										<span className="text-fg-tertiary flex cursor-pointer items-center truncate text-xs font-normal">
											<span>Learn More</span>
											<ChevronRight className="size-4" />
										</span>
									</div>
								</div>
							</SidebarMenuButton>
						</HoverCardTrigger>
						<HoverCardContent
							side="right"
							align="end"
							sideOffset={4}
							className="w-60 rounded-xl p-2">
							<InfoCard />
						</HoverCardContent>
					</HoverCard>
				</div>

				<SidebarGroup>
					<SidebarGroupContent>
						<SidebarMenu>
							{footerData.map((section, idx) => (
								<React.Fragment key={idx}>
									{section.title && (
										<SidebarGroupLabel>{section.title}</SidebarGroupLabel>
									)}

									{section.items.map((item) => (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton tooltip={item.label} asChild>
												<a href={item.href}>
													{item.icon && <item.icon />}
													<span>{item.label}</span>
												</a>
											</SidebarMenuButton>
										</SidebarMenuItem>
									))}
								</React.Fragment>
							))}
						</SidebarMenu>
					</SidebarGroupContent>
				</SidebarGroup>
			</SidebarContent>

			<SidebarFooter className="gap-0 p-0">
				<SidebarSeparator className="w-full" />

				<SidebarFooterUser />
			</SidebarFooter>
		</Sidebar>
	)
}

Installation

pnpm dlx radianui@latest add sidebar

Usage

Imports

import {
	Sidebar,
	SidebarContent,
	SidebarFooter,
	SidebarGroup,
	SidebarGroupContent,
	SidebarGroupLabel,
	SidebarHeader,
	SidebarInset,
	SidebarMenu,
	SidebarMenuBadge,
	SidebarMenuButton,
	SidebarMenuItem,
	SidebarProvider,
	SidebarTrigger,
} from "@/registry/ui/sidebar"

Code

export default function AppLayout({ children }) {
	return (
		<SidebarProvider>
			<Sidebar>
				<SidebarHeader />
				<SidebarContent>
					<SidebarGroup>
						<SidebarGroupLabel />
						<SidebarGroupContent />
					</SidebarGroup>
				</SidebarContent>
 
				<SidebarFooter />
			</Sidebar>
			<SidebarInset>
				<header>
					<SidebarTrigger />
				</header>
				{children}
			</SidebarInset>
		</SidebarProvider>
	)
}

Examples

Floating Sidebar

Files
components/app-sidebar.tsx
"use client"

import React, { ComponentType } from "react"
import {
	Box,
	Calendar,
	ChevronDown,
	ChevronRight,
	ClipboardList,
	FileChartColumn,
	Headset,
	Inbox,
	Search,
	Settings,
	TvMinimal,
	Users2,
} from "lucide-react"
import { Badge } from "@/registry/ui/badge"
import { IconButton } from "@/registry/ui/button"
import {
	Dropdown,
	DropdownContent,
	DropdownItem,
	DropdownLabel,
	DropdownTrigger,
} from "@/registry/ui/dropdown"
import {
	HoverCard,
	HoverCardContent,
	HoverCardTrigger,
} from "@/registry/ui/hover-card"
import { Input, InputWrapper } from "@/registry/ui/input"
import {
	Sidebar,
	SidebarCollapsible,
	SidebarCollapsibleContent,
	SidebarCollapsibleTrigger,
	SidebarContent,
	SidebarFooter,
	SidebarGroup,
	SidebarGroupContent,
	SidebarGroupLabel,
	SidebarHeader,
	SidebarMenu,
	SidebarMenuBadge,
	SidebarMenuButton,
	SidebarMenuItem,
	SidebarMenuSub,
	SidebarMenuSubItem,
	SidebarSeparator,
	SidebarTrigger,
	useSidebar,
} from "@/registry/ui/sidebar"
import { InfoCard } from "./info-card"
import {
	AcmeLogo,
	CircleLogo,
	DiscordLogo,
	DriveLogo,
	Logo,
	MageLogo,
	NotionLogo,
	RadianCoreLogo,
} from "./logos"
import { SidebarFooterUser } from "./sidebar-footer-user"

interface SubItem {
	label: string
	href: string
	icon: ComponentType<{ className?: string }>
}

interface NavItem {
	label: string
	icon: ComponentType<{ className?: string }>
	href?: string
	subitems?: SubItem[]
	isActive?: boolean
	badge?: React.ReactNode
}

interface NavGroup {
	title: string | null
	items: NavItem[]
}

const mainData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Home",
				icon: TvMinimal,
				href: "#",
			},
			{
				label: "Inbox",
				icon: Inbox,
				href: "#",
				isActive: true,
				badge: 4,
			},
			{
				label: "Calendar",
				icon: Calendar,
				href: "#",
			},
			{
				label: "Analytics",
				icon: FileChartColumn,
				href: "#",
			},
		],
	},
	{
		title: "Extension",
		items: [
			{
				label: "Subscribers",
				icon: Users2,
				href: "#",
			},
			{
				label: "Reports",
				icon: ClipboardList,
				href: "#",
			},
			{
				label: "Integrations",
				icon: Box,
				subitems: [
					{
						label: "Notion",
						icon: NotionLogo,
						href: "#",
					},
					{
						label: "Google Drive",
						icon: DriveLogo,
						href: "#",
					},
					{
						label: "Discord",
						icon: DiscordLogo,
						href: "#",
					},
				],
			},
		],
	},
	{
		title: "Projects",
		items: [
			{
				icon: MageLogo,
				label: "Mage Icons",
				href: "#",
			},
			{
				icon: AcmeLogo,
				label: "Acme Inc",
				href: "#",
			},
			{
				icon: RadianCoreLogo,
				label: "Radian Core",
				href: "#",
			},
		],
	},
]

const footerData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Help Center",
				icon: Headset,
				href: "#",
			},
			{
				label: "Settings",
				icon: Settings,
				href: "#",
			},
		],
	},
]

export function AppSidebar() {
	const { setOpen, state, isMobile } = useSidebar()
	const inputRef = React.useRef<HTMLInputElement>(null)
	// For opening dropdown on hover
	const [openItem, setOpenItem] = React.useState<string | null>(null)
	const [hoverOpen, setHoverOpen] = React.useState(false)
	const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)

	const openMenu = (title: string) => {
		if (timeoutRef.current) clearTimeout(timeoutRef.current)
		setOpenItem(title)
	}

	const closeMenu = () => {
		timeoutRef.current = setTimeout(() => {
			setOpenItem(null)
		}, 150)
	}

	return (
		<Sidebar collapsible="icon" variant="floating">
			<SidebarHeader className="p-0">
				<div className="group/header relative flex items-center gap-2 px-2.5 pb-2 pt-4 group-data-[state=expanded]:pl-5 group-data-[state=expanded]:pr-3">
					<div className="z-0 group-data-[state=collapsed]:px-2 group-data-[state=collapsed]:py-1 group-hover/header:group-data-[state=collapsed]:opacity-0">
						<Logo />
					</div>
					<span className="truncate font-semibold group-data-[collapsible=icon]:hidden">
						Debcon
					</span>
					<SidebarTrigger
						size="32"
						className="group-hover/header:opacity-100! z-10 ml-auto group-data-[collapsible=icon]:absolute group-data-[collapsible=icon]:left-4 group-data-[collapsible=icon]:top-4 group-data-[collapsible=icon]:ml-0 group-data-[state=collapsed]:opacity-0"
					/>
				</div>

				<div className="w-full px-3 py-2">
					<InputWrapper
						className="group-data-[state=collapsed]:hidden"
						size="36">
						<Search className="text-fg-tertiary" />
						<Input ref={inputRef} type="search" placeholder="Search" />
						<Badge size="20" color="neutral" variant="outline">
							⌘ /
						</Badge>
					</InputWrapper>

					<IconButton
						onClick={() => {
							setOpen(true)
							setTimeout(() => {
								inputRef.current?.focus()
							}, 200)
						}}
						size="36"
						variant="outline"
						color="neutral"
						className="group-data-[mobile=true]:hidden group-data-[state=expanded]:hidden">
						<Search className="text-fg-tertiary" />
					</IconButton>
				</div>
			</SidebarHeader>
			<SidebarContent>
				{mainData.map((section, idx) => (
					<SidebarGroup key={idx}>
						{section.title && (
							<SidebarGroupLabel className="uppercase">
								{section.title}
							</SidebarGroupLabel>
						)}
						<SidebarMenu>
							{section.items.map((item) => {
								if (!item.subitems) {
									return (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton
												size="32"
												isActive={item.isActive}
												tooltip={item.label}
												asChild>
												<a href={item.href}>
													{item.icon && <item.icon className="size-5" />}
													<span>{item.label}</span>
													{item.badge && (
														<SidebarMenuBadge
															variant="outline"
															color="neutral"
															className="bg-bg!">
															{item.badge}
														</SidebarMenuBadge>
													)}
												</a>
											</SidebarMenuButton>
										</SidebarMenuItem>
									)
								}

								if (state === "collapsed" && !isMobile) {
									return (
										<Dropdown
											open={openItem === item.label}
											onOpenChange={() => {}}
											modal={false}
											key={item.label}>
											<DropdownTrigger className="group/trigger w-full" asChild>
												<SidebarMenuButton
													onMouseEnter={() => openMenu(item.label)}
													onMouseLeave={closeMenu}
													onPointerDown={(e) => e.preventDefault()}>
													{item.icon && <item.icon />}
												</SidebarMenuButton>
											</DropdownTrigger>
											<DropdownContent
												onMouseEnter={() => openMenu(item.label)}
												onMouseLeave={closeMenu}
												side="right"
												className="w-60"
												align="center">
												{item.label && (
													<DropdownLabel>{item.label}</DropdownLabel>
												)}

												{item.subitems.map((subitem) => (
													<DropdownItem
														key={subitem.label}
														className="[&_svg]:size-5!"
														asChild>
														<a href={subitem.href}>
															<subitem.icon />
															{subitem.label}
														</a>
													</DropdownItem>
												))}
											</DropdownContent>
										</Dropdown>
									)
								}

								return (
									<SidebarCollapsible key={item.label}>
										<SidebarMenuItem>
											<SidebarCollapsibleTrigger className="w-full" asChild>
												<SidebarMenuButton tooltip={item.label}>
													{item.icon && <item.icon />}
													<span>{item.label}</span>
													<ChevronDown className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180" />
												</SidebarMenuButton>
											</SidebarCollapsibleTrigger>
											<SidebarCollapsibleContent>
												<SidebarMenuSub>
													{item.subitems.map((subitem) => (
														<SidebarMenuSubItem key={subitem.label}>
															<SidebarMenuButton asChild>
																<a href={subitem.href}>
																	<subitem.icon />
																	{subitem.label}
																</a>
															</SidebarMenuButton>
														</SidebarMenuSubItem>
													))}
												</SidebarMenuSub>
											</SidebarCollapsibleContent>
										</SidebarMenuItem>
									</SidebarCollapsible>
								)
							})}
						</SidebarMenu>
					</SidebarGroup>
				))}

				<div className="mt-auto p-2 px-3 pb-1.5 group-data-[state=collapsed]:px-3.5">
					<HoverCard
						open={state === "expanded" ? false : hoverOpen}
						onOpenChange={setHoverOpen}>
						<HoverCardTrigger asChild>
							<SidebarMenuButton
								className="bg-elevation-level2! h-auto border px-2.5 py-1.5 group-data-[state=expanded]:cursor-default"
								asChild>
								<div>
									<CircleLogo />
									<div className="flex flex-col">
										<span className="text-[13px] font-medium leading-5">
											Version 1.2 Update
										</span>
										<span className="text-fg-tertiary flex cursor-pointer items-center truncate text-xs font-normal">
											<span>Learn More</span>
											<ChevronRight className="size-4" />
										</span>
									</div>
								</div>
							</SidebarMenuButton>
						</HoverCardTrigger>
						<HoverCardContent
							side="right"
							align="end"
							sideOffset={4}
							className="w-60 rounded-xl p-2">
							<InfoCard />
						</HoverCardContent>
					</HoverCard>
				</div>

				<SidebarGroup>
					<SidebarGroupContent>
						<SidebarMenu>
							{footerData.map((section, idx) => (
								<React.Fragment key={idx}>
									{section.title && (
										<SidebarGroupLabel>{section.title}</SidebarGroupLabel>
									)}

									{section.items.map((item) => (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton tooltip={item.label}>
												{item.icon && <item.icon />}
												<span>{item.label}</span>
											</SidebarMenuButton>
										</SidebarMenuItem>
									))}
								</React.Fragment>
							))}
						</SidebarMenu>
					</SidebarGroupContent>
				</SidebarGroup>
			</SidebarContent>

			<SidebarFooter className="gap-0 p-0">
				<SidebarSeparator className="w-full" />

				<SidebarFooterUser />
			</SidebarFooter>
		</Sidebar>
	)
}

Inset Sidebar

Files
components/app-sidebar.tsx
"use client"

import React, { ComponentType } from "react"
import {
	Box,
	Calendar,
	ChevronDown,
	ChevronRight,
	ClipboardList,
	FileChartColumn,
	Headset,
	Inbox,
	Search,
	Settings,
	TvMinimal,
	Users2,
} from "lucide-react"
import { Badge } from "@/registry/ui/badge"
import { IconButton } from "@/registry/ui/button"
import {
	Dropdown,
	DropdownContent,
	DropdownItem,
	DropdownLabel,
	DropdownTrigger,
} from "@/registry/ui/dropdown"
import {
	HoverCard,
	HoverCardContent,
	HoverCardTrigger,
} from "@/registry/ui/hover-card"
import { Input, InputWrapper } from "@/registry/ui/input"
import {
	Sidebar,
	SidebarCollapsible,
	SidebarCollapsibleContent,
	SidebarCollapsibleTrigger,
	SidebarContent,
	SidebarFooter,
	SidebarGroup,
	SidebarGroupContent,
	SidebarGroupLabel,
	SidebarHeader,
	SidebarMenu,
	SidebarMenuBadge,
	SidebarMenuButton,
	SidebarMenuItem,
	SidebarMenuSub,
	SidebarMenuSubItem,
	SidebarSeparator,
	SidebarTrigger,
	useSidebar,
} from "@/registry/ui/sidebar"
import { InfoCard } from "./info-card"
import {
	AcmeLogo,
	CircleLogo,
	DiscordLogo,
	DriveLogo,
	Logo,
	MageLogo,
	NotionLogo,
	RadianCoreLogo,
} from "./logos"
import { SidebarFooterUser } from "./sidebar-footer-user"

interface SubItem {
	label: string
	href: string
	icon: ComponentType<{ className?: string }>
}

interface NavItem {
	label: string
	icon: ComponentType<{ className?: string }>
	href?: string
	subitems?: SubItem[]
	isActive?: boolean
	badge?: React.ReactNode
}

interface NavGroup {
	title: string | null
	items: NavItem[]
}

const mainData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Home",
				icon: TvMinimal,
				href: "#",
			},
			{
				label: "Inbox",
				icon: Inbox,
				href: "#",
				isActive: true,
				badge: 4,
			},
			{
				label: "Calendar",
				icon: Calendar,
				href: "#",
			},
			{
				label: "Analytics",
				icon: FileChartColumn,
				href: "#",
			},
		],
	},
	{
		title: "Extension",
		items: [
			{
				label: "Subscribers",
				icon: Users2,
				href: "#",
			},
			{
				label: "Reports",
				icon: ClipboardList,
				href: "#",
			},
			{
				label: "Integrations",
				icon: Box,
				subitems: [
					{
						label: "Notion",
						icon: NotionLogo,
						href: "#",
					},
					{
						label: "Google Drive",
						icon: DriveLogo,
						href: "#",
					},
					{
						label: "Discord",
						icon: DiscordLogo,
						href: "#",
					},
				],
			},
		],
	},
	{
		title: "Projects",
		items: [
			{
				icon: MageLogo,
				label: "Mage Icons",
				href: "#",
			},
			{
				icon: AcmeLogo,
				label: "Acme Inc",
				href: "#",
			},
			{
				icon: RadianCoreLogo,
				label: "Radian Core",
				href: "#",
			},
		],
	},
]

const footerData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Help Center",
				icon: Headset,
				href: "#",
			},
			{
				label: "Settings",
				icon: Settings,
				href: "#",
			},
		],
	},
]

export function AppSidebar() {
	const { setOpen, state, isMobile } = useSidebar()
	const inputRef = React.useRef<HTMLInputElement>(null)
	// For opening dropdown on hover
	const [hoverOpen, setHoverOpen] = React.useState(false)
	const [openItem, setOpenItem] = React.useState<string | null>(null)
	const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)

	const openMenu = (title: string) => {
		if (timeoutRef.current) clearTimeout(timeoutRef.current)
		setOpenItem(title)
	}

	const closeMenu = () => {
		timeoutRef.current = setTimeout(() => {
			setOpenItem(null)
		}, 150)
	}

	return (
		<Sidebar className="px-0" collapsible="icon" variant="inset">
			<SidebarHeader className="p-0">
				<div className="group/header relative flex items-center gap-2 px-2.5 pb-2 pt-4 group-data-[state=expanded]:pl-5 group-data-[state=expanded]:pr-3">
					<div className="z-0 group-data-[state=collapsed]:px-2 group-data-[state=collapsed]:py-1 group-hover/header:group-data-[state=collapsed]:opacity-0">
						<Logo />
					</div>
					<span className="truncate font-semibold group-data-[collapsible=icon]:hidden">
						Debcon
					</span>
					<SidebarTrigger
						size="32"
						className="group-hover/header:opacity-100! z-10 ml-auto group-data-[collapsible=icon]:absolute group-data-[collapsible=icon]:left-4 group-data-[collapsible=icon]:top-4 group-data-[collapsible=icon]:ml-0 group-data-[state=collapsed]:opacity-0"
					/>
				</div>

				<div className="w-full px-3 py-2">
					<InputWrapper
						className="group-data-[state=collapsed]:hidden"
						size="36">
						<Search className="text-fg-tertiary" />
						<Input ref={inputRef} type="search" placeholder="Search" />
						<Badge size="20" color="neutral" variant="outline">
							⌘ /
						</Badge>
					</InputWrapper>

					<IconButton
						onClick={() => {
							setOpen(true)
							setTimeout(() => {
								inputRef.current?.focus()
							}, 200)
						}}
						size="36"
						variant="outline"
						color="neutral"
						className="group-data-[mobile=true]:hidden group-data-[state=expanded]:hidden">
						<Search className="text-fg-tertiary" />
					</IconButton>
				</div>
			</SidebarHeader>
			<SidebarContent>
				{mainData.map((section, idx) => (
					<SidebarGroup key={idx}>
						{section.title && (
							<SidebarGroupLabel className="uppercase">
								{section.title}
							</SidebarGroupLabel>
						)}
						<SidebarMenu>
							{section.items.map((item) => {
								if (!item.subitems) {
									return (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton
												size="32"
												isActive={item.isActive}
												tooltip={item.label}
												asChild>
												<a href={item.href}>
													{item.icon && <item.icon className="size-5" />}
													<span>{item.label}</span>
													{item.badge && (
														<SidebarMenuBadge
															variant="outline"
															color="neutral"
															className="bg-bg!">
															{item.badge}
														</SidebarMenuBadge>
													)}
												</a>
											</SidebarMenuButton>
										</SidebarMenuItem>
									)
								}

								if (state === "collapsed" && !isMobile) {
									return (
										<Dropdown
											open={openItem === item.label}
											onOpenChange={() => {}}
											modal={false}
											key={item.label}>
											<DropdownTrigger className="group/trigger w-full" asChild>
												<SidebarMenuButton
													onMouseEnter={() => openMenu(item.label)}
													onMouseLeave={closeMenu}
													onPointerDown={(e) => e.preventDefault()}>
													{item.icon && <item.icon />}
												</SidebarMenuButton>
											</DropdownTrigger>
											<DropdownContent
												onMouseEnter={() => openMenu(item.label)}
												onMouseLeave={closeMenu}
												side="right"
												className="w-60"
												align="center">
												{item.label && (
													<DropdownLabel>{item.label}</DropdownLabel>
												)}

												{item.subitems.map((subitem) => (
													<DropdownItem
														key={subitem.label}
														className="[&_svg]:size-5!"
														asChild>
														<a href={subitem.href}>
															<subitem.icon />
															{subitem.label}
														</a>
													</DropdownItem>
												))}
											</DropdownContent>
										</Dropdown>
									)
								}

								return (
									<SidebarCollapsible key={item.label}>
										<SidebarMenuItem>
											<SidebarCollapsibleTrigger className="w-full" asChild>
												<SidebarMenuButton tooltip={item.label}>
													{item.icon && <item.icon />}
													<span>{item.label}</span>
													<ChevronDown className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180" />
												</SidebarMenuButton>
											</SidebarCollapsibleTrigger>
											<SidebarCollapsibleContent>
												<SidebarMenuSub>
													{item.subitems.map((subitem) => (
														<SidebarMenuSubItem key={subitem.label}>
															<SidebarMenuButton asChild>
																<a href={subitem.href}>
																	<subitem.icon />
																	{subitem.label}
																</a>
															</SidebarMenuButton>
														</SidebarMenuSubItem>
													))}
												</SidebarMenuSub>
											</SidebarCollapsibleContent>
										</SidebarMenuItem>
									</SidebarCollapsible>
								)
							})}
						</SidebarMenu>
					</SidebarGroup>
				))}

				<div className="mt-auto p-2 px-3 pb-1.5 group-data-[state=collapsed]:px-3.5">
					<HoverCard
						open={state === "expanded" ? false : hoverOpen}
						onOpenChange={setHoverOpen}>
						<HoverCardTrigger asChild>
							<SidebarMenuButton
								className="bg-elevation-level2! h-auto border px-2.5 py-1.5 group-data-[state=expanded]:cursor-default"
								asChild>
								<div>
									<CircleLogo />
									<div className="flex flex-col">
										<span className="text-[13px] font-medium leading-5">
											Version 1.2 Update
										</span>
										<span className="text-fg-tertiary flex cursor-pointer items-center truncate text-xs font-normal">
											<span>Learn More</span>
											<ChevronRight className="size-4" />
										</span>
									</div>
								</div>
							</SidebarMenuButton>
						</HoverCardTrigger>
						<HoverCardContent
							side="right"
							align="end"
							sideOffset={4}
							className="w-60 rounded-xl p-2">
							<InfoCard />
						</HoverCardContent>
					</HoverCard>
				</div>

				<SidebarGroup>
					<SidebarGroupContent>
						<SidebarMenu>
							{footerData.map((section, idx) => (
								<React.Fragment key={idx}>
									{section.title && (
										<SidebarGroupLabel>{section.title}</SidebarGroupLabel>
									)}

									{section.items.map((item) => (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton tooltip={item.label}>
												{item.icon && <item.icon />}
												<span>{item.label}</span>
											</SidebarMenuButton>
										</SidebarMenuItem>
									))}
								</React.Fragment>
							))}
						</SidebarMenu>
					</SidebarGroupContent>
				</SidebarGroup>
			</SidebarContent>

			<SidebarFooter className="gap-0 p-0">
				<SidebarSeparator className="w-full" />

				<SidebarFooterUser />
			</SidebarFooter>
		</Sidebar>
	)
}

Inverse Sidebar

Files
components/app-sidebar.tsx
"use client"

import React, { ComponentType } from "react"
import {
	Box,
	Calendar,
	ChevronDown,
	ChevronRight,
	ClipboardList,
	FileChartColumn,
	Headset,
	Inbox,
	Search,
	Settings,
	TvMinimal,
	Users2,
} from "lucide-react"
import { Badge } from "@/registry/ui/badge"
import { IconButton } from "@/registry/ui/button"
import {
	Dropdown,
	DropdownContent,
	DropdownItem,
	DropdownLabel,
	DropdownTrigger,
} from "@/registry/ui/dropdown"
import {
	HoverCard,
	HoverCardContent,
	HoverCardTrigger,
} from "@/registry/ui/hover-card"
import { Input, InputWrapper } from "@/registry/ui/input"
import {
	Sidebar,
	SidebarCollapsible,
	SidebarCollapsibleContent,
	SidebarCollapsibleTrigger,
	SidebarContent,
	SidebarFooter,
	SidebarGroup,
	SidebarGroupContent,
	SidebarGroupLabel,
	SidebarHeader,
	SidebarMenu,
	SidebarMenuBadge,
	SidebarMenuButton,
	SidebarMenuItem,
	SidebarMenuSub,
	SidebarMenuSubItem,
	SidebarSeparator,
	SidebarTrigger,
	useSidebar,
} from "@/registry/ui/sidebar"
import { InfoCard } from "./info-card"
import {
	AcmeLogo,
	CircleLogo,
	DiscordLogo,
	DriveLogo,
	Logo,
	MageLogo,
	NotionLogo,
	RadianCoreLogo,
} from "./logos"
import { SidebarFooterUser } from "./sidebar-footer-user"

interface SubItem {
	label: string
	href: string
	icon: ComponentType<{ className?: string }>
}

interface NavItem {
	label: string
	icon: ComponentType<{ className?: string }>
	href?: string
	subitems?: SubItem[]
	isActive?: boolean
	badge?: React.ReactNode
}

interface NavGroup {
	title: string | null
	items: NavItem[]
}

const mainData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Home",
				icon: TvMinimal,
				href: "#",
			},
			{
				label: "Inbox",
				icon: Inbox,
				href: "#",
				isActive: true,
				badge: 4,
			},
			{
				label: "Calendar",
				icon: Calendar,
				href: "#",
			},
			{
				label: "Analytics",
				icon: FileChartColumn,
				href: "#",
			},
		],
	},
	{
		title: "Extension",
		items: [
			{
				label: "Subscribers",
				icon: Users2,
				href: "#",
			},
			{
				label: "Reports",
				icon: ClipboardList,
				href: "#",
			},
			{
				label: "Integrations",
				icon: Box,
				subitems: [
					{
						label: "Notion",
						icon: NotionLogo,
						href: "#",
					},
					{
						label: "Google Drive",
						icon: DriveLogo,
						href: "#",
					},
					{
						label: "Discord",
						icon: DiscordLogo,
						href: "#",
					},
				],
			},
		],
	},
	{
		title: "Projects",
		items: [
			{
				icon: MageLogo,
				label: "Mage Icons",
				href: "#",
			},
			{
				icon: AcmeLogo,
				label: "Acme Inc",
				href: "#",
			},
			{
				icon: RadianCoreLogo,
				label: "Radian Core",
				href: "#",
			},
		],
	},
]

const footerData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Help Center",
				icon: Headset,
				href: "#",
			},
			{
				label: "Settings",
				icon: Settings,
				href: "#",
			},
		],
	},
]

export function AppSidebar() {
	const { setOpen, state, isMobile } = useSidebar()
	const inputRef = React.useRef<HTMLInputElement>(null)
	// For opening dropdown on hover
	const [openItem, setOpenItem] = React.useState<string | null>(null)
	const [hoverOpen, setHoverOpen] = React.useState(false)
	const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)

	const openMenu = (title: string) => {
		if (timeoutRef.current) clearTimeout(timeoutRef.current)
		setOpenItem(title)
	}

	const closeMenu = () => {
		timeoutRef.current = setTimeout(() => {
			setOpenItem(null)
		}, 150)
	}

	return (
		<Sidebar collapsible="icon" theme="inverse">
			<SidebarHeader className="p-0">
				<div className="group/header relative flex items-center gap-2 px-2.5 pb-2 pt-4 group-data-[state=expanded]:pl-5 group-data-[state=expanded]:pr-3">
					<div className="z-0 group-data-[state=collapsed]:px-2 group-data-[state=collapsed]:py-1 group-hover/header:group-data-[state=collapsed]:opacity-0">
						<Logo />
					</div>
					<span className="truncate font-semibold group-data-[collapsible=icon]:hidden">
						Debcon
					</span>
					<SidebarTrigger
						size="32"
						className="group-hover/header:opacity-100! z-10 ml-auto group-data-[collapsible=icon]:absolute group-data-[collapsible=icon]:left-4 group-data-[collapsible=icon]:top-4 group-data-[collapsible=icon]:ml-0 group-data-[state=collapsed]:opacity-0"
					/>
				</div>

				<div className="w-full px-3 py-2">
					<InputWrapper
						className="group-data-[state=collapsed]:hidden"
						size="36">
						<Search className="text-fg-tertiary" />
						<Input ref={inputRef} type="search" placeholder="Search" />
						<Badge size="20" color="neutral" variant="outline">
							⌘ /
						</Badge>
					</InputWrapper>

					<IconButton
						onClick={() => {
							setOpen(true)
							setTimeout(() => {
								inputRef.current?.focus()
							}, 200)
						}}
						size="36"
						variant="outline"
						color="neutral"
						className="group-data-[mobile=true]:hidden group-data-[state=expanded]:hidden">
						<Search className="text-fg-tertiary" />
					</IconButton>
				</div>
			</SidebarHeader>
			<SidebarContent>
				{mainData.map((section, idx) => (
					<SidebarGroup key={idx}>
						{section.title && (
							<SidebarGroupLabel className="uppercase">
								{section.title}
							</SidebarGroupLabel>
						)}
						<SidebarMenu>
							{section.items.map((item) => {
								if (!item.subitems) {
									return (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton
												size="32"
												isActive={item.isActive}
												tooltip={item.label}
												asChild>
												<a href={item.href}>
													{item.icon && <item.icon className="size-5" />}
													<span>{item.label}</span>
													{item.badge && (
														<SidebarMenuBadge
															variant="outline"
															color="neutral"
															className="bg-bg!">
															{item.badge}
														</SidebarMenuBadge>
													)}
												</a>
											</SidebarMenuButton>
										</SidebarMenuItem>
									)
								}

								if (state === "collapsed" && !isMobile) {
									return (
										<Dropdown
											open={openItem === item.label}
											onOpenChange={() => {}}
											modal={false}
											key={item.label}>
											<DropdownTrigger className="group/trigger w-full" asChild>
												<SidebarMenuButton
													onMouseEnter={() => openMenu(item.label)}
													onMouseLeave={closeMenu}
													onPointerDown={(e) => e.preventDefault()}>
													{item.icon && <item.icon />}
												</SidebarMenuButton>
											</DropdownTrigger>
											<DropdownContent
												onMouseEnter={() => openMenu(item.label)}
												onMouseLeave={closeMenu}
												side="right"
												className="w-60"
												align="center">
												{item.label && (
													<DropdownLabel>{item.label}</DropdownLabel>
												)}

												{item.subitems.map((subitem) => (
													<DropdownItem
														key={subitem.label}
														className="[&_svg]:size-5!"
														asChild>
														<a href={subitem.href}>
															<subitem.icon />
															{subitem.label}
														</a>
													</DropdownItem>
												))}
											</DropdownContent>
										</Dropdown>
									)
								}

								return (
									<SidebarCollapsible key={item.label}>
										<SidebarMenuItem>
											<SidebarCollapsibleTrigger className="w-full" asChild>
												<SidebarMenuButton tooltip={item.label}>
													{item.icon && <item.icon />}
													<span>{item.label}</span>
													<ChevronDown className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180" />
												</SidebarMenuButton>
											</SidebarCollapsibleTrigger>
											<SidebarCollapsibleContent>
												<SidebarMenuSub>
													{item.subitems.map((subitem) => (
														<SidebarMenuSubItem key={subitem.label}>
															<SidebarMenuButton asChild>
																<a href={subitem.href}>
																	<subitem.icon />
																	{subitem.label}
																</a>
															</SidebarMenuButton>
														</SidebarMenuSubItem>
													))}
												</SidebarMenuSub>
											</SidebarCollapsibleContent>
										</SidebarMenuItem>
									</SidebarCollapsible>
								)
							})}
						</SidebarMenu>
					</SidebarGroup>
				))}

				<div className="mt-auto p-2 px-3 pb-1.5 group-data-[state=collapsed]:px-3.5">
					<HoverCard
						open={state === "expanded" ? false : hoverOpen}
						onOpenChange={setHoverOpen}>
						<HoverCardTrigger asChild>
							<SidebarMenuButton
								className="bg-elevation-level2! h-auto border px-2.5 py-1.5 group-data-[state=expanded]:cursor-default"
								asChild>
								<div>
									<CircleLogo />
									<div className="flex flex-col">
										<span className="text-[13px] font-medium leading-5">
											Version 1.2 Update
										</span>
										<span className="text-fg-tertiary flex cursor-pointer items-center truncate text-xs font-normal">
											<span>Learn More</span>
											<ChevronRight className="size-4" />
										</span>
									</div>
								</div>
							</SidebarMenuButton>
						</HoverCardTrigger>
						<HoverCardContent
							side="right"
							align="end"
							sideOffset={4}
							className="w-60 rounded-xl p-2">
							<InfoCard />
						</HoverCardContent>
					</HoverCard>
				</div>

				<SidebarGroup>
					<SidebarGroupContent>
						<SidebarMenu>
							{footerData.map((section, idx) => (
								<React.Fragment key={idx}>
									{section.title && (
										<SidebarGroupLabel>{section.title}</SidebarGroupLabel>
									)}

									{section.items.map((item) => (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton tooltip={item.label}>
												{item.icon && <item.icon />}
												<span>{item.label}</span>
											</SidebarMenuButton>
										</SidebarMenuItem>
									))}
								</React.Fragment>
							))}
						</SidebarMenu>
					</SidebarGroupContent>
				</SidebarGroup>
			</SidebarContent>

			<SidebarFooter className="gap-0 p-0">
				<SidebarSeparator className="w-full" />

				<SidebarFooterUser />
			</SidebarFooter>
		</Sidebar>
	)
}

Offcanvas Sidebar

Files
components/app-sidebar.tsx
"use client"

import React, { ComponentType } from "react"
import {
	Box,
	Calendar,
	ChevronDown,
	ClipboardList,
	FileChartColumn,
	Headset,
	Inbox,
	Search,
	Settings,
	TvMinimal,
	Users2,
} from "lucide-react"
import { Badge } from "@/registry/ui/badge"
import { Input, InputWrapper } from "@/registry/ui/input"
import {
	Sidebar,
	SidebarCollapsible,
	SidebarCollapsibleContent,
	SidebarCollapsibleTrigger,
	SidebarContent,
	SidebarFooter,
	SidebarGroup,
	SidebarGroupContent,
	SidebarGroupLabel,
	SidebarHeader,
	SidebarMenu,
	SidebarMenuBadge,
	SidebarMenuButton,
	SidebarMenuItem,
	SidebarMenuSub,
	SidebarMenuSubItem,
	SidebarSeparator,
} from "@/registry/ui/sidebar"
import { InfoCardExpanded } from "./info-card-expanded"
import {
	AcmeLogo,
	DiscordLogo,
	DriveLogo,
	Logo,
	MageLogo,
	NotionLogo,
	RadianCoreLogo,
} from "./logos"
import { SidebarFooterUser } from "./sidebar-footer-user"

interface SubItem {
	label: string
	href: string
	icon: ComponentType<{ className?: string }>
}

interface NavItem {
	label: string
	icon: ComponentType<{ className?: string }>
	href?: string
	subitems?: SubItem[]
	isActive?: boolean
	badge?: React.ReactNode
}

interface NavGroup {
	title: string | null
	items: NavItem[]
}

const mainData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Home",
				icon: TvMinimal,
				href: "#",
			},
			{
				label: "Inbox",
				icon: Inbox,
				href: "#",
				isActive: true,
				badge: 4,
			},
			{
				label: "Calendar",
				icon: Calendar,
				href: "#",
			},
			{
				label: "Analytics",
				icon: FileChartColumn,
				href: "#",
			},
		],
	},
	{
		title: "Extension",
		items: [
			{
				label: "Subscribers",
				icon: Users2,
				href: "#",
			},
			{
				label: "Reports",
				icon: ClipboardList,
				href: "#",
			},
			{
				label: "Integrations",
				icon: Box,
				subitems: [
					{
						label: "Notion",
						icon: NotionLogo,
						href: "#",
					},
					{
						label: "Google Drive",
						icon: DriveLogo,
						href: "#",
					},
					{
						label: "Discord",
						icon: DiscordLogo,
						href: "#",
					},
				],
			},
		],
	},
	{
		title: "Projects",
		items: [
			{
				icon: MageLogo,
				label: "Mage Icons",
				href: "#",
			},
			{
				icon: AcmeLogo,
				label: "Acme Inc",
				href: "#",
			},
			{
				icon: RadianCoreLogo,
				label: "Radian Core",
				href: "#",
			},
		],
	},
]

const footerData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Help Center",
				icon: Headset,
				href: "#",
			},
			{
				label: "Settings",
				icon: Settings,
				href: "#",
			},
		],
	},
]

export function AppSidebar() {
	return (
		<Sidebar collapsible="offcanvas">
			<SidebarHeader className="p-0">
				<div className="group/header relative flex items-center gap-2 px-2.5 pb-2 pl-5 pt-4">
					<div>
						<Logo />
					</div>
					<span className="truncate font-semibold">Debcon</span>
				</div>

				<div className="w-full px-3 py-2">
					<InputWrapper size="36">
						<Search className="text-fg-tertiary" />
						<Input type="search" placeholder="Search" />
						<Badge size="20" color="neutral" variant="outline">
							⌘ /
						</Badge>
					</InputWrapper>
				</div>
			</SidebarHeader>
			<SidebarContent>
				{mainData.map((section, idx) => (
					<SidebarGroup key={idx}>
						{section.title && (
							<SidebarGroupLabel className="uppercase">
								{section.title}
							</SidebarGroupLabel>
						)}
						<SidebarMenu>
							{section.items.map((item) => {
								if (!item.subitems) {
									return (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton
												size="32"
												isActive={item.isActive}
												tooltip={item.label}
												asChild>
												<a href={item.href}>
													{item.icon && <item.icon className="size-5" />}
													<span>{item.label}</span>
													{item.badge && (
														<SidebarMenuBadge
															variant="outline"
															color="neutral"
															className="bg-bg!">
															{item.badge}
														</SidebarMenuBadge>
													)}
												</a>
											</SidebarMenuButton>
										</SidebarMenuItem>
									)
								}

								return (
									<SidebarCollapsible key={item.label}>
										<SidebarMenuItem>
											<SidebarCollapsibleTrigger className="w-full" asChild>
												<SidebarMenuButton tooltip={item.label}>
													{item.icon && <item.icon />}
													<span>{item.label}</span>
													<ChevronDown className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180" />
												</SidebarMenuButton>
											</SidebarCollapsibleTrigger>
											<SidebarCollapsibleContent>
												<SidebarMenuSub>
													{item.subitems.map((subitem) => (
														<SidebarMenuSubItem key={subitem.label}>
															<SidebarMenuButton asChild>
																<a href={subitem.href}>
																	<subitem.icon />
																	{subitem.label}
																</a>
															</SidebarMenuButton>
														</SidebarMenuSubItem>
													))}
												</SidebarMenuSub>
											</SidebarCollapsibleContent>
										</SidebarMenuItem>
									</SidebarCollapsible>
								)
							})}
						</SidebarMenu>
					</SidebarGroup>
				))}

				<InfoCardExpanded />

				<SidebarGroup>
					<SidebarGroupContent>
						<SidebarMenu>
							{footerData.map((section, idx) => (
								<React.Fragment key={idx}>
									{section.title && (
										<SidebarGroupLabel>{section.title}</SidebarGroupLabel>
									)}

									{section.items.map((item) => (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton tooltip={item.label}>
												{item.icon && <item.icon />}
												<span>{item.label}</span>
											</SidebarMenuButton>
										</SidebarMenuItem>
									))}
								</React.Fragment>
							))}
						</SidebarMenu>
					</SidebarGroupContent>
				</SidebarGroup>
			</SidebarContent>

			<SidebarFooter className="gap-0 p-0">
				<SidebarSeparator className="w-full" />

				<SidebarFooterUser />
			</SidebarFooter>
		</Sidebar>
	)
}

Resizable Sidebar

Files
components/app-sidebar.tsx
"use client"

import React, { ComponentType } from "react"
import {
	Box,
	Calendar,
	ChevronDown,
	ClipboardList,
	FileChartColumn,
	Headset,
	Inbox,
	Search,
	Settings,
	TvMinimal,
	Users2,
} from "lucide-react"
import { Badge } from "@/registry/ui/badge"
import { Input, InputWrapper } from "@/registry/ui/input"
import {
	Sidebar,
	SidebarCollapsible,
	SidebarCollapsibleContent,
	SidebarCollapsibleTrigger,
	SidebarContent,
	SidebarFooter,
	SidebarGroup,
	SidebarGroupContent,
	SidebarGroupLabel,
	SidebarHeader,
	SidebarMenu,
	SidebarMenuBadge,
	SidebarMenuButton,
	SidebarMenuItem,
	SidebarMenuSub,
	SidebarMenuSubItem,
	SidebarSeparator,
} from "@/registry/ui/sidebar"
import { InfoCardExpanded } from "./info-card-expanded"
import {
	AcmeLogo,
	DiscordLogo,
	DriveLogo,
	Logo,
	MageLogo,
	NotionLogo,
	RadianCoreLogo,
} from "./logos"
import { SidebarFooterUser } from "./sidebar-footer-user"

interface SubItem {
	label: string
	href: string
	icon: ComponentType<{ className?: string }>
}

interface NavItem {
	label: string
	icon: ComponentType<{ className?: string }>
	href?: string
	subitems?: SubItem[]
	isActive?: boolean
	badge?: React.ReactNode
}

interface NavGroup {
	title: string | null
	items: NavItem[]
}

const mainData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Home",
				icon: TvMinimal,
				href: "#",
			},
			{
				label: "Inbox",
				icon: Inbox,
				href: "#",
				isActive: true,
				badge: 4,
			},
			{
				label: "Calendar",
				icon: Calendar,
				href: "#",
			},
			{
				label: "Analytics",
				icon: FileChartColumn,
				href: "#",
			},
		],
	},
	{
		title: "Extension",
		items: [
			{
				label: "Subscribers",
				icon: Users2,
				href: "#",
			},
			{
				label: "Reports",
				icon: ClipboardList,
				href: "#",
			},
			{
				label: "Integrations",
				icon: Box,
				subitems: [
					{
						label: "Notion",
						icon: NotionLogo,
						href: "#",
					},
					{
						label: "Google Drive",
						icon: DriveLogo,
						href: "#",
					},
					{
						label: "Discord",
						icon: DiscordLogo,
						href: "#",
					},
				],
			},
		],
	},
	{
		title: "Projects",
		items: [
			{
				icon: MageLogo,
				label: "Mage Icons",
				href: "#",
			},
			{
				icon: AcmeLogo,
				label: "Acme Inc",
				href: "#",
			},
			{
				icon: RadianCoreLogo,
				label: "Radian Core",
				href: "#",
			},
		],
	},
]

const footerData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Help Center",
				icon: Headset,
				href: "#",
			},
			{
				label: "Settings",
				icon: Settings,
				href: "#",
			},
		],
	},
]

export function AppSidebar() {
	return (
		<Sidebar
			variant="sidebar"
			collapsible="offcanvas"
			resizable
			minWidth={250}
			maxWidth={500}>
			<SidebarHeader className="p-0">
				<div className="group/header relative flex items-center gap-2 px-2.5 pb-2 pl-5 pt-4">
					<div>
						<Logo />
					</div>
					<span className="truncate font-semibold">Debcon</span>
				</div>

				<div className="w-full px-3 py-2">
					<InputWrapper size="36">
						<Search className="text-fg-tertiary" />
						<Input type="search" placeholder="Search" />
						<Badge size="20" color="neutral" variant="outline">
							⌘ /
						</Badge>
					</InputWrapper>
				</div>
			</SidebarHeader>
			<SidebarContent>
				{mainData.map((section, idx) => (
					<SidebarGroup key={idx}>
						{section.title && (
							<SidebarGroupLabel className="uppercase">
								{section.title}
							</SidebarGroupLabel>
						)}
						<SidebarMenu>
							{section.items.map((item) => {
								if (!item.subitems) {
									return (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton
												size="32"
												isActive={item.isActive}
												tooltip={item.label}
												asChild>
												<a href={item.href}>
													{item.icon && <item.icon className="size-5" />}
													<span>{item.label}</span>
													{item.badge && (
														<SidebarMenuBadge
															variant="outline"
															color="neutral"
															className="bg-bg!">
															{item.badge}
														</SidebarMenuBadge>
													)}
												</a>
											</SidebarMenuButton>
										</SidebarMenuItem>
									)
								}

								return (
									<SidebarCollapsible key={item.label}>
										<SidebarMenuItem>
											<SidebarCollapsibleTrigger className="w-full" asChild>
												<SidebarMenuButton tooltip={item.label}>
													{item.icon && <item.icon />}
													<span>{item.label}</span>
													<ChevronDown className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180" />
												</SidebarMenuButton>
											</SidebarCollapsibleTrigger>
											<SidebarCollapsibleContent>
												<SidebarMenuSub>
													{item.subitems.map((subitem) => (
														<SidebarMenuSubItem key={subitem.label}>
															<SidebarMenuButton asChild>
																<a href={subitem.href}>
																	<subitem.icon />
																	{subitem.label}
																</a>
															</SidebarMenuButton>
														</SidebarMenuSubItem>
													))}
												</SidebarMenuSub>
											</SidebarCollapsibleContent>
										</SidebarMenuItem>
									</SidebarCollapsible>
								)
							})}
						</SidebarMenu>
					</SidebarGroup>
				))}

				<InfoCardExpanded />

				<SidebarGroup>
					<SidebarGroupContent>
						<SidebarMenu>
							{footerData.map((section, idx) => (
								<React.Fragment key={idx}>
									{section.title && (
										<SidebarGroupLabel>{section.title}</SidebarGroupLabel>
									)}

									{section.items.map((item) => (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton tooltip={item.label}>
												{item.icon && <item.icon />}
												<span>{item.label}</span>
											</SidebarMenuButton>
										</SidebarMenuItem>
									))}
								</React.Fragment>
							))}
						</SidebarMenu>
					</SidebarGroupContent>
				</SidebarGroup>
			</SidebarContent>

			<SidebarFooter className="gap-0 p-0">
				<SidebarSeparator className="w-full" />

				<SidebarFooterUser />
			</SidebarFooter>
		</Sidebar>
	)
}
Files
components/app-sidebar.tsx
"use client"

import React, { ComponentType } from "react"
import {
	Box,
	Calendar,
	ChevronDown,
	ChevronRight,
	ClipboardList,
	FileChartColumn,
	Headset,
	Inbox,
	Search,
	Settings,
	TvMinimal,
	Users2,
} from "lucide-react"
import { Badge } from "@/registry/ui/badge"
import { IconButton } from "@/registry/ui/button"
import {
	Dropdown,
	DropdownContent,
	DropdownItem,
	DropdownLabel,
	DropdownTrigger,
} from "@/registry/ui/dropdown"
import {
	HoverCard,
	HoverCardContent,
	HoverCardTrigger,
} from "@/registry/ui/hover-card"
import { Input, InputWrapper } from "@/registry/ui/input"
import {
	Sidebar,
	SidebarCollapsible,
	SidebarCollapsibleContent,
	SidebarCollapsibleTrigger,
	SidebarContent,
	SidebarFooter,
	SidebarGroup,
	SidebarGroupContent,
	SidebarGroupLabel,
	SidebarHeader,
	SidebarMenu,
	SidebarMenuBadge,
	SidebarMenuButton,
	SidebarMenuItem,
	SidebarMenuSub,
	SidebarMenuSubItem,
	SidebarRail,
	SidebarSeparator,
	SidebarTrigger,
	useSidebar,
} from "@/registry/ui/sidebar"
import { InfoCard } from "./info-card"
import {
	AcmeLogo,
	CircleLogo,
	DiscordLogo,
	DriveLogo,
	Logo,
	MageLogo,
	NotionLogo,
	RadianCoreLogo,
} from "./logos"
import { SidebarFooterUser } from "./sidebar-footer-user"

interface SubItem {
	label: string
	href: string
	icon: ComponentType<{ className?: string }>
}

interface NavItem {
	label: string
	icon: ComponentType<{ className?: string }>
	href?: string
	subitems?: SubItem[]
	isActive?: boolean
	badge?: React.ReactNode
}

interface NavGroup {
	title: string | null
	items: NavItem[]
}

const mainData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Home",
				icon: TvMinimal,
				href: "#",
			},
			{
				label: "Inbox",
				icon: Inbox,
				href: "#",
				isActive: true,
				badge: 4,
			},
			{
				label: "Calendar",
				icon: Calendar,
				href: "#",
			},
			{
				label: "Analytics",
				icon: FileChartColumn,
				href: "#",
			},
		],
	},
	{
		title: "Extension",
		items: [
			{
				label: "Subscribers",
				icon: Users2,
				href: "#",
			},
			{
				label: "Reports",
				icon: ClipboardList,
				href: "#",
			},
			{
				label: "Integrations",
				icon: Box,
				subitems: [
					{
						label: "Notion",
						icon: NotionLogo,
						href: "#",
					},
					{
						label: "Google Drive",
						icon: DriveLogo,
						href: "#",
					},
					{
						label: "Discord",
						icon: DiscordLogo,
						href: "#",
					},
				],
			},
		],
	},
	{
		title: "Projects",
		items: [
			{
				icon: MageLogo,
				label: "Mage Icons",
				href: "#",
			},
			{
				icon: AcmeLogo,
				label: "Acme Inc",
				href: "#",
			},
			{
				icon: RadianCoreLogo,
				label: "Radian Core",
				href: "#",
			},
		],
	},
]

const footerData: NavGroup[] = [
	{
		title: null,
		items: [
			{
				label: "Help Center",
				icon: Headset,
				href: "#",
			},
			{
				label: "Settings",
				icon: Settings,
				href: "#",
			},
		],
	},
]

export function AppSidebar() {
	const { setOpen, state, isMobile } = useSidebar()
	const inputRef = React.useRef<HTMLInputElement>(null)
	// For opening dropdown on hover
	const [openItem, setOpenItem] = React.useState<string | null>(null)
	const [hoverOpen, setHoverOpen] = React.useState(false)
	const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)

	const openMenu = (title: string) => {
		if (timeoutRef.current) clearTimeout(timeoutRef.current)
		setOpenItem(title)
	}

	const closeMenu = () => {
		timeoutRef.current = setTimeout(() => {
			setOpenItem(null)
		}, 150)
	}

	return (
		<Sidebar collapsible="icon" variant="sidebar">
			<SidebarHeader className="p-0">
				<div className="group/header relative flex items-center gap-2 px-2.5 pb-2 pt-4 group-data-[state=expanded]:pl-5 group-data-[state=expanded]:pr-3">
					<div className="z-0 group-data-[state=collapsed]:px-2 group-data-[state=collapsed]:py-1 group-hover/header:group-data-[state=collapsed]:opacity-0">
						<Logo />
					</div>
					<span className="truncate font-semibold group-data-[collapsible=icon]:hidden">
						Debcon
					</span>
					<SidebarTrigger
						size="32"
						className="group-hover/header:opacity-100! z-10 ml-auto group-data-[collapsible=icon]:absolute group-data-[collapsible=icon]:left-4 group-data-[collapsible=icon]:top-4 group-data-[collapsible=icon]:ml-0 group-data-[state=collapsed]:opacity-0"
					/>
				</div>

				<div className="w-full px-3 py-2">
					<InputWrapper
						className="group-data-[state=collapsed]:hidden"
						size="36">
						<Search className="text-fg-tertiary" />
						<Input ref={inputRef} type="search" placeholder="Search" />
						<Badge size="20" color="neutral" variant="outline">
							⌘ /
						</Badge>
					</InputWrapper>

					<IconButton
						onClick={() => {
							setOpen(true)
							setTimeout(() => {
								inputRef.current?.focus()
							}, 200)
						}}
						size="36"
						variant="outline"
						color="neutral"
						className="group-data-[mobile=true]:hidden group-data-[state=expanded]:hidden">
						<Search className="text-fg-tertiary" />
					</IconButton>
				</div>
			</SidebarHeader>
			<SidebarContent>
				{mainData.map((section, idx) => (
					<SidebarGroup key={idx}>
						{section.title && (
							<SidebarGroupLabel className="uppercase">
								{section.title}
							</SidebarGroupLabel>
						)}
						<SidebarMenu>
							{section.items.map((item) => {
								if (!item.subitems) {
									return (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton
												size="32"
												isActive={item.isActive}
												tooltip={item.label}
												asChild>
												<a href={item.href}>
													{item.icon && <item.icon className="size-5" />}
													<span>{item.label}</span>
													{item.badge && (
														<SidebarMenuBadge
															variant="outline"
															color="neutral"
															className="bg-bg!">
															{item.badge}
														</SidebarMenuBadge>
													)}
												</a>
											</SidebarMenuButton>
										</SidebarMenuItem>
									)
								}

								if (state === "collapsed" && !isMobile) {
									return (
										<Dropdown
											open={openItem === item.label}
											onOpenChange={() => {}}
											modal={false}
											key={item.label}>
											<DropdownTrigger className="group/trigger w-full" asChild>
												<SidebarMenuButton
													onMouseEnter={() => openMenu(item.label)}
													onMouseLeave={closeMenu}
													onPointerDown={(e) => e.preventDefault()}>
													{item.icon && <item.icon />}
												</SidebarMenuButton>
											</DropdownTrigger>
											<DropdownContent
												onMouseEnter={() => openMenu(item.label)}
												onMouseLeave={closeMenu}
												side="right"
												className="w-60"
												align="center">
												{item.label && (
													<DropdownLabel>{item.label}</DropdownLabel>
												)}

												{item.subitems.map((subitem) => (
													<DropdownItem
														key={subitem.label}
														className="[&_svg]:size-5!"
														asChild>
														<a href={subitem.href}>
															<subitem.icon />
															{subitem.label}
														</a>
													</DropdownItem>
												))}
											</DropdownContent>
										</Dropdown>
									)
								}

								return (
									<SidebarCollapsible key={item.label}>
										<SidebarMenuItem>
											<SidebarCollapsibleTrigger className="w-full" asChild>
												<SidebarMenuButton tooltip={item.label}>
													{item.icon && <item.icon />}
													<span>{item.label}</span>
													<ChevronDown className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180" />
												</SidebarMenuButton>
											</SidebarCollapsibleTrigger>
											<SidebarCollapsibleContent>
												<SidebarMenuSub>
													{item.subitems.map((subitem) => (
														<SidebarMenuSubItem key={subitem.label}>
															<SidebarMenuButton asChild>
																<a href={subitem.href}>
																	<subitem.icon />
																	{subitem.label}
																</a>
															</SidebarMenuButton>
														</SidebarMenuSubItem>
													))}
												</SidebarMenuSub>
											</SidebarCollapsibleContent>
										</SidebarMenuItem>
									</SidebarCollapsible>
								)
							})}
						</SidebarMenu>
					</SidebarGroup>
				))}

				<div className="mt-auto p-2 px-3 pb-1.5 group-data-[state=collapsed]:px-3.5">
					<HoverCard
						open={state === "expanded" ? false : hoverOpen}
						onOpenChange={setHoverOpen}>
						<HoverCardTrigger asChild>
							<SidebarMenuButton
								className="bg-elevation-level2! h-auto border px-2.5 py-1.5 group-data-[state=expanded]:cursor-default"
								asChild>
								<div>
									<CircleLogo />
									<div className="flex flex-col">
										<span className="text-[13px] font-medium leading-5">
											Version 1.2 Update
										</span>
										<span className="text-fg-tertiary flex cursor-pointer items-center truncate text-xs font-normal">
											<span>Learn More</span>
											<ChevronRight className="size-4" />
										</span>
									</div>
								</div>
							</SidebarMenuButton>
						</HoverCardTrigger>
						<HoverCardContent
							side="right"
							align="end"
							sideOffset={4}
							className="w-60 rounded-xl p-2">
							<InfoCard />
						</HoverCardContent>
					</HoverCard>
				</div>

				<SidebarGroup>
					<SidebarGroupContent>
						<SidebarMenu>
							{footerData.map((section, idx) => (
								<React.Fragment key={idx}>
									{section.title && (
										<SidebarGroupLabel>{section.title}</SidebarGroupLabel>
									)}

									{section.items.map((item) => (
										<SidebarMenuItem key={item.label}>
											<SidebarMenuButton tooltip={item.label}>
												{item.icon && <item.icon />}
												<span>{item.label}</span>
											</SidebarMenuButton>
										</SidebarMenuItem>
									))}
								</React.Fragment>
							))}
						</SidebarMenu>
					</SidebarGroupContent>
				</SidebarGroup>
			</SidebarContent>

			<SidebarFooter className="gap-0 p-0">
				<SidebarSeparator className="w-full" />

				<SidebarFooterUser />
			</SidebarFooter>

			<SidebarRail />
		</Sidebar>
	)
}

SidebarProvider

The root context component. Must wrap the entire layout, both the Sidebar and the SidebarInset.

PropTypeDescription
defaultOpenbooleanUncontrolled initial open state
openbooleanControlled open state
onOpenChange(open: boolean) => voidCalled when open state changes
shortcutstringShortcut key to open sidebar

The sidebar panel itself. Renders as a fixed-position column on desktop and as a Drawer on mobile. Accepts theme, variant, collapsible, and side props to control its appearance and behavior.

PropTypeDefaultDescription
themeneutral-accent | neutral-white |
white-on-grey | neutral-dark
neutral-accentColor theme
sideleft | rightleftWhich edge to anchor to
variantsidebar | floating | insetsidebarVisual shape
collapsibleoffcanvas | icon | noneoffcanvasCollapse behavior
resizablebooleanfalseResizable behavior
minWidthnumber250Minimum width(in px)
maxWidthnumber500Maximum width(in px)

SidebarHeader

A sticky header component to add to the sidebar.

<SidebarHeader>
	<SidebarMenuButton size="48" className="justify-between">
		<div className="flex items-center gap-2">
			<div className="bg-primary size-6 rounded" />
			<div>
				<p className="text-sm font-semibold">Acme Inc.</p>
				<p className="text-fg-secondary text-xs">Startup plan</p>
			</div>
		</div>
		<ChevronsUpDownIcon />
	</SidebarMenuButton>
</SidebarHeader>

SidebarContent

The scrollable body of the sidebar.

<SidebarContent>
	<SidebarGroup>
		<SidebarGroupLabel>Platform</SidebarGroupLabel>
		<SidebarGroupContent>
			<SidebarMenu>
				<SidebarMenuItem>
					<SidebarMenuButton asChild isActive>
						<a href="/dashboard">
							<LayoutDashboard />
							<span>Dashboard</span>
						</a>
					</SidebarMenuButton>
				</SidebarMenuItem>
				<SidebarMenuItem>
					<SidebarMenuButton asChild>
						<a href="/inbox">
							<Inbox />
							<span>Inbox</span>
						</a>
					</SidebarMenuButton>
					<SidebarMenuBadge>12</SidebarMenuBadge>
				</SidebarMenuItem>
			</SidebarMenu>
		</SidebarGroupContent>
	</SidebarGroup>
</SidebarContent>

SidebarFooter

A sticky footer component to add to the sidebar.

<SidebarFooter>
	<Dropdown>
		<DropdownTrigger asChild>
			<SidebarMenuButton size="48">
				<span>{user.name}</span>
				<ChevronsUpDownIcon className="ml-auto" />
			</SidebarMenuButton>
		</DropdownTrigger>
		<DropdownContent>
			<DropdownItem>Profile</DropdownItem>
			<DropdownItem>Settings</DropdownItem>
			<DropdownItem>Sign out</DropdownItem>
		</DropdownContent>
	</Dropdown>
</SidebarFooter>

SidebarGroup

A layout wrapper for a logical section of navigation items inside SidebarContent. Use multiple groups to organize nav items into sections.

<SidebarGroup>
	<SidebarGroupLabel>Platform</SidebarGroupLabel>
	<SidebarGroupContent>
		<SidebarMenu>...</SidebarMenu>
	</SidebarGroupContent>
</SidebarGroup>

SidebarGroupAction

An icon button anchored to the top-right of a SidebarGroup. Useful for section-level actions like adding a new item to a list.

<SidebarGroup>
	<SidebarGroupLabel>Projects</SidebarGroupLabel>
	<SidebarGroupAction>
		<PlusIcon />
	</SidebarGroupAction>
	<SidebarGroupContent>...</SidebarGroupContent>
</SidebarGroup>

SidebarMenu

The SidebarMenu component is used for building a menu within a SidebarGroup.

<SidebarMenu>
	<SidebarMenuItem>...</SidebarMenuItem>
	<SidebarMenuItem>...</SidebarMenuItem>
</SidebarMenu>

SidebarMenuItem

An element that wraps a single menu item.

<SidebarMenuItem>
	<SidebarMenuButton>Dashboard</SidebarMenuButton>
</SidebarMenuItem>

SidebarMenuButton

The primary interactive element inside a SidebarMenuItem. Accepts asChild to render as a link, isActive to mark the current page, and tooltip as a fallback label in icon collapsible mode.

<SidebarMenuButton asChild isActive tooltip="Dashboard">
	<a href="/dashboard">
		<LayoutDashboard />
		<span>Dashboard</span>
	</a>
</SidebarMenuButton>

SidebarMenuAction

The SidebarMenuAction component is used to render a menu action within a SidebarMenuItem. Use for item-level actions like renaming, deleting, or opening a context menu.

<SidebarMenuItem>
	<SidebarMenuButton asChild>
		<a href="/project/1">
			<Folder />
			<span>Project Alpha</span>
		</a>
	</SidebarMenuButton>
	<SidebarMenuAction>
		<MoreHorizontalIcon />
	</SidebarMenuAction>
</SidebarMenuItem>

SidebarMenuSub

The SidebarMenuSub component is used to render a submenu within a SidebarMenu.

<SidebarMenuItem>
	<SidebarMenuButton>Components</SidebarMenuButton>
	<SidebarMenuSub>
		<SidebarMenuSubItem>
			<SidebarMenuSubButton>Button</SidebarMenuSubButton>
		</SidebarMenuSubItem>
	</SidebarMenuSub>
</SidebarMenuItem>

SidebarCollapsible

A component for building expandable nav groups.

<SidebarCollapsible defaultOpen>
	<SidebarCollapsibleTrigger>Settings</SidebarCollapsibleTrigger>
	<SidebarCollapsibleContent>
		<SidebarMenu>...</SidebarMenu>
	</SidebarCollapsibleContent>
</SidebarCollapsible>

SidebarSkeleton

useSidebar

The useSidebar hook is used to control the sidebar.

PropertyTypeDescription
state"expanded" or "collapsed"Current visual state
openbooleanWhether sidebar is open on desktop
setOpen(open: boolean) => voidOpen or close programmatically
openMobilebooleanWhether the mobile Drawer is open
setOpenMobile(open: boolean) => voidOpen or close mobile Drawer
isMobilebooleanTrue when viewport is below 768px
toggleSidebar() => voidToggles desktop sidebar or mobile drawer
function SidebarLogo() {
	const { state } = useSidebar()
	return state === "expanded" ? <FullLogo /> : <IconMark />
}

Theming

Modify the sidebarThemeVars object inside sidebar.tsx file to add custom sidebar themes.

const sidebarThemeVars: Record<
	NonNullable<SidebarProps["theme"]>,
	React.CSSProperties> = {
	"neutral-white": {
		"--color-sidebar": "var(--color-bg)",
	} as React.CSSProperties,
	...,
+ 	"your-new-theme": {
+		// custom styles
+	}
}

You can modify the following CSS variables.

:root {
	--color-sidebar: var(--color-fill1);
	--color-sidebar-fg: var(--color-fg);
	--color-sidebar-accent: var(--color-fill1);
	--color-sidebar-accent-fg: var(--color-fg);
	--color-sidebar-border: var(--color-border);
	--color-sidebar-ring: var(--color-primary-border);
}

Mobile Behavior

Below 768px, Sidebar renders as a Drawer automatically. The drawer direction matches the side prop.

function AppHeader() {
	const { toggleSidebar, isMobile } = useSidebar()
 
	return (
		<header>
			{isMobile && (
				<Button onClick={toggleSidebar} aria-label="Open menu">
					<MenuIcon />
				</Button>
			)}
		</header>
	)
}

Override mobile drawer width

<SidebarProvider style={{ "--sidebar-width-mobile": "22rem" }}>

The mobile Drawer includes a visually hidden DialogTitle for screen reader support. Cookie-based persistence only applies on desktop, mobile Drawer state is session-only.