Zubeyr··ComponentsnavigationlayoutSidebar
A dashboard sidebar for light and dark mode: brand header, titled groups, counts and external links.
Live demo ↗Installation
Run the shadcn CLI in a project that already has components.json. Using the @tomei tab? Register the namespace once first.
pnpm dlx shadcn@latest add https://tomei.zubeyr.dev/ui/r/sidebar.json- →Files land in
components/dashboard.
Usage
import {
BarChart3,
BookOpen,
Home,
Megaphone,
Package,
Search,
Settings,
ShoppingBag,
Store,
Tag,
Users,
} from 'lucide-react'
import { Input } from '@/registry/dashboard/components/input/input'
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
SidebarItem,
} from '@/components/dashboard/sidebar'
export default function Demo() {
return (
<div className="fixed inset-0 flex bg-background">
<Sidebar>
<SidebarHeader
logo={<span className="size-8 rounded-lg bg-sidebar-primary-foreground" />}
title="Tomei"
subtitle="Store admin"
/>
<SidebarContent>
<SidebarGroup>
<SidebarItem icon={<Home />}>Home</SidebarItem>
<SidebarItem icon={<ShoppingBag />} count={15}>
Orders
</SidebarItem>
<SidebarItem
icon={<Package />}
submenu={
<>
<SidebarItem>Collections</SidebarItem>
<SidebarItem>Inventory</SidebarItem>
</>
}
>
Products
</SidebarItem>
<SidebarItem icon={<Users />}>Customers</SidebarItem>
<SidebarItem icon={<BarChart3 />} active>
Analytics
</SidebarItem>
</SidebarGroup>
<SidebarGroup title="Growth">
<SidebarItem icon={<Megaphone />}>Marketing</SidebarItem>
<SidebarItem icon={<Tag />}>Discounts</SidebarItem>
</SidebarGroup>
<SidebarGroup title="Sales channels">
<SidebarItem icon={<Store />}>Online store</SidebarItem>
<SidebarItem icon={<ShoppingBag />}>Point of sale</SidebarItem>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarItem icon={<BookOpen />} href="#" external>
Documentation
</SidebarItem>
<SidebarItem icon={<Settings />}>Settings</SidebarItem>
</SidebarFooter>
</Sidebar>
<main className="flex min-w-0 flex-1 flex-col">
<header className="flex items-center justify-between gap-4 border-b px-6 py-3">
<h1 className="text-base font-medium">Analytics</h1>
<Input placeholder="Search" leading={<Search />} className="max-w-xs" />
</header>
<div className="grid flex-1 place-items-center text-sm text-muted-foreground">
Your page content
</div>
</main>
</div>
)
}Source code
import { cn } from '@/lib/utils'
export function Sidebar({ className, ...props }: React.ComponentProps<'aside'>) {
return (
<aside
className={cn(
'flex w-60 shrink-0 flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground',
className,
)}
{...props}
/>
)
}
export function SidebarHeader({
logo,
title,
subtitle,
href,
className,
}: {
logo?: React.ReactNode
title: React.ReactNode
subtitle?: React.ReactNode
href?: string
className?: string
}) {
const Tag = href ? 'a' : 'div'
return (
<Tag
href={href}
className={cn(
'flex shrink-0 items-center gap-2 border-b border-sidebar-border bg-sidebar-accent p-3',
className,
)}
>
{logo && <span className="flex size-8 shrink-0 items-center justify-center">{logo}</span>}
<span className="flex min-w-0 flex-col gap-1">
<span className="truncate text-sm leading-none font-medium text-sidebar-accent-foreground">
{title}
</span>
{subtitle && <span className="truncate text-xs leading-none">{subtitle}</span>}
</span>
</Tag>
)
}
export function SidebarContent({ className, ...props }: React.ComponentProps<'nav'>) {
return (
<nav
className={cn(
'min-h-0 flex-1 overflow-y-auto [&>*:not(:last-child)]:border-b [&>*:not(:last-child)]:border-sidebar-border',
className,
)}
{...props}
/>
)
}
export function SidebarGroup({
title,
action,
className,
children,
}: {
title?: React.ReactNode
action?: React.ReactNode
className?: string
children: React.ReactNode
}) {
return (
<div className={cn('flex flex-col gap-1 p-3', className)}>
{title && (
<div className="flex items-center justify-between px-2 py-1">
<span className="text-xs leading-none font-semibold tracking-wide text-sidebar-foreground uppercase">
{title}
</span>
{action}
</div>
)}
<ul className="flex flex-col gap-px">{children}</ul>
</div>
)
}
export function SidebarItem({
as,
href,
icon,
count,
active = false,
external = false,
submenu,
className,
children,
...props
}: {
as?: React.ElementType
href?: string
icon?: React.ReactNode
count?: number
active?: boolean
external?: boolean
submenu?: React.ReactNode
} & React.HTMLAttributes<HTMLElement>) {
const Tag = as ?? (href ? 'a' : 'button')
return (
<li>
<Tag
href={href}
type={Tag === 'button' ? 'button' : undefined}
data-active={active}
aria-current={active ? 'page' : undefined}
{...(external && { target: '_blank', rel: 'noreferrer' })}
className={cn(
'group flex h-8 w-full items-center gap-2 rounded-lg px-2 text-left text-sm transition-colors duration-150 outline-none hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring data-[active=true]:bg-sidebar-primary data-[active=true]:text-sidebar-primary-foreground [&_svg]:size-4 [&_svg]:shrink-0',
className,
)}
{...props}
>
{icon && (
<span className="flex opacity-80 group-hover:text-sidebar-accent-foreground group-data-[active=true]:text-sidebar-primary-foreground">
{icon}
</span>
)}
<span className="min-w-0 flex-1 truncate">{children}</span>
{count !== undefined && (
<span className="inline-flex h-4 min-w-6 items-center justify-center rounded-full border border-sidebar-border bg-sidebar-accent px-1 text-[11px] tabular-nums">
{count}
</span>
)}
{external && (
<span aria-hidden className="opacity-60">
↗
</span>
)}
</Tag>
{submenu && <ul className="mt-px flex flex-col gap-px pl-6">{submenu}</ul>}
</li>
)
}
export function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
className={cn('flex shrink-0 flex-col gap-px border-t border-sidebar-border p-3', className)}
{...props}
/>
)
}