- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Avatar
- Badge
- Banner
- Breadcrumb
- Button
- Calendar
- Card
- Carousel
- Checkbox
- Code Area
- Collapsible
- Combobox
- Command
- Currency Input
- Date Picker
- Dialog
- Divider
- Drawer
- Dropdown
- Empty
- File Upload
- Form
- Hover Card
- Input
- Label
- OTP Field
- Pagination
- Phone Number Input
- Popover
- Progress
- Radio Group
- Resizable
- Scroll Area
- Select
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Text Area
- Tooltip
- FadeComing Soon
- Infinite ScrollComing Soon
- PointerComing Soon
"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
"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
"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
"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
"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
"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>
)
}
Sidebar with Rail
"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.
| Prop | Type | Description |
|---|---|---|
defaultOpen | boolean | Uncontrolled initial open state |
open | boolean | Controlled open state |
onOpenChange | (open: boolean) => void | Called when open state changes |
shortcut | string | Shortcut key to open sidebar |
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.
| Prop | Type | Default | Description |
|---|---|---|---|
theme | neutral-accent | neutral-white | white-on-grey | neutral-dark | neutral-accent | Color theme |
side | left | right | left | Which edge to anchor to |
variant | sidebar | floating | inset | sidebar | Visual shape |
collapsible | offcanvas | icon | none | offcanvas | Collapse behavior |
resizable | boolean | false | Resizable behavior |
minWidth | number | 250 | Minimum width(in px) |
maxWidth | number | 500 | Maximum 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.
| Property | Type | Description |
|---|---|---|
state | "expanded" or "collapsed" | Current visual state |
open | boolean | Whether sidebar is open on desktop |
setOpen | (open: boolean) => void | Open or close programmatically |
openMobile | boolean | Whether the mobile Drawer is open |
setOpenMobile | (open: boolean) => void | Open or close mobile Drawer |
isMobile | boolean | True when viewport is below 768px |
toggleSidebar | () => void | Toggles 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
Drawerincludes a visually hiddenDialogTitlefor screen reader support. Cookie-based persistence only applies on desktop, mobile Drawer state is session-only.