Components
- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Date Picker
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input OTP
- Input Group
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toggle
- Toggle Group
- Tooltip
- New Component
Elements
Charts
Icons
Navigation
- 4
Main content area
import { HomeIcon, InboxIcon, LayoutDashboardIcon, SearchIcon, SettingsIcon, UsersIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarSeparator, SidebarTrigger } from "@/components/ui/sidebar";
const navItems = [
{ active: false, icon: HomeIcon, label: "Home" },
{ active: true, icon: LayoutDashboardIcon, label: "Dashboard" },
{ active: false, badge: "4", icon: InboxIcon, label: "Inbox" },
{ active: false, icon: UsersIcon, label: "Team" },
{ active: false, icon: SettingsIcon, label: "Settings" },
];
export const SidebarDemo = () => (
<SidebarProvider className="h-[480px] min-h-0 overflow-hidden rounded-lg border contain-strict">
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" tooltip="GIN Design">
<div className="bg-primary text-primary-foreground flex size-8 shrink-0 items-center justify-center rounded-md text-xs font-bold">GIN</div>
<span className="font-semibold">GIN Design</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup><SidebarGroupContent><SidebarInput placeholder="Search…" /></SidebarGroupContent></SidebarGroup>
<SidebarSeparator />
<SidebarGroup>
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{navItems.map((item) => (
<SidebarMenuItem key={item.label}>
<SidebarMenuButton isActive={item.active} tooltip={item.label}>
<item.icon />
<span>{item.label}</span>
</SidebarMenuButton>
{item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton tooltip="Account" size="lg">
<div className="bg-muted size-8 shrink-0 rounded-full" />
<div className="flex flex-col text-left text-xs leading-tight">
<span className="font-medium">Nuno Santos</span>
<span className="text-muted-foreground truncate">nunosantos@nunosantos.pt</span>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
<SidebarInset>
<header className="flex h-12 items-center gap-2 border-b px-4">
<SidebarTrigger />
<span className="text-sm font-medium">Dashboard</span>
</header>
<div className="flex flex-1 items-center justify-center p-8">
<div className="text-muted-foreground flex flex-col items-center gap-2 text-center text-sm">
<LayoutDashboardIcon className="size-8 opacity-30" />
<span>Main content area</span>
<Button variant="outline" size="sm">
<SearchIcon />
Browse components
</Button>
</div>
</div>
</SidebarInset>
</SidebarProvider>
);Layout
Wrap your page with SidebarProvider and place Sidebar and SidebarInset as siblings. SidebarProvider manages open state, keyboard shortcut (⌘B), and cookie persistence.
<SidebarProvider>
<Sidebar>...</Sidebar>
<SidebarInset>
<header>
<SidebarTrigger />
</header>
{/* page content */}
</SidebarInset>
</SidebarProvider>Collapsible
Set collapsible on Sidebar to control collapse behaviour.
<Sidebar collapsible="offcanvas"> {/* slides fully off-screen (default) */}
<Sidebar collapsible="icon"> {/* collapses to icon-only width */}
<Sidebar collapsible="none"> {/* always visible, never collapses */}Variant
<Sidebar variant="sidebar"> {/* default — full bleed to viewport edge */}
<Sidebar variant="floating"> {/* floating card with rounded corners + shadow */}
<Sidebar variant="inset"> {/* main content has rounded-xl inset */}Side
<Sidebar side="left"> {/* default */}
<Sidebar side="right">Navigation structure
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton isActive tooltip="Dashboard">
<LayoutDashboardIcon />
<span>Dashboard</span>
</SidebarMenuButton>
<SidebarMenuBadge>4</SidebarMenuBadge>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>Tooltips on collapsed icon mode
Pass a tooltip string (or TooltipContent props) to SidebarMenuButton. Tooltips auto-show only when the sidebar is collapsed to icon mode.
<SidebarMenuButton tooltip="Settings">
<SettingsIcon />
<span>Settings</span>
</SidebarMenuButton>Sub-menu
<SidebarMenuItem>
<SidebarMenuButton>
<FolderIcon />
<span>Projects</span>
</SidebarMenuButton>
<SidebarMenuSub>
<SidebarMenuSubItem>
<SidebarMenuSubButton isActive>Website</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuSubButton>Mobile app</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</SidebarMenuItem>Menu action
Use SidebarMenuAction for a secondary icon button that appears on hover.
<SidebarMenuItem>
<SidebarMenuButton>
<FolderIcon /><span>Projects</span>
</SidebarMenuButton>
<SidebarMenuAction showOnHover>
<PlusIcon />
</SidebarMenuAction>
</SidebarMenuItem>Loading skeleton
Use SidebarMenuSkeleton while content is loading.
{isLoading ? (
Array.from({ length: 5 }).map((_, i) => (
<SidebarMenuItem key={i}>
<SidebarMenuSkeleton showIcon />
</SidebarMenuItem>
))
) : (
navItems.map(...)
)}Controlled
const [open, setOpen] = useState(true);
<SidebarProvider open={open} onOpenChange={setOpen}>
...
</SidebarProvider>useSidebar hook
Access sidebar state from any child component.
const { state, open, toggleSidebar, isMobile } = useSidebar();
// state: "expanded" | "collapsed"Props
SidebarProvider
| Prop | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | true | Uncontrolled initial state |
open | boolean | — | Controlled open state |
onOpenChange | (open: boolean) => void | — | Callback on state change |
Sidebar
| Prop | Type | Default | Description |
|---|---|---|---|
side | "left" | "right" | "left" | Which side the sidebar is on |
variant | "sidebar" | "floating" | "inset" | "sidebar" | Visual variant |
collapsible | "offcanvas" | "icon" | "none" | "offcanvas" | Collapse behaviour |
SidebarMenuButton
| Prop | Type | Default | Description |
|---|---|---|---|
isActive | boolean | false | Highlights as the active route |
variant | "default" | "outline" | "default" | Visual style |
size | "sm" | "default" | "lg" | "default" | Button height |
tooltip | string | TooltipContentProps | — | Tooltip shown in icon-collapsed mode |
asChild | boolean | false | Render as child element (e.g. <Link>) |
sound | FeedbackType | — | Audio feedback on click |
Usage
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";Specs
# Mint — Sidebar Specs
## Component
- name: sidebar
- file: registry/mint/sidebar.tsx
- installs to: components/ui/sidebar.tsx
- dependencies: radix-ui, lucide-react, class-variance-authority
- registryDependencies: button, input, separator, sheet, skeleton, tooltip
## What is it
A full app sidebar system composed of 20+ sub-components orchestrated by
SidebarProvider context. Supports desktop expand/collapse (offcanvas or icon
mode), mobile Sheet overlay, keyboard shortcut (⌘B), and cookie-persisted
open state. Prefer this system for app shells with persistent navigation; do
not use for simple dropdowns or transient menus.
## Visual identity
Off-white sidebar surface (--sidebar) sits slightly apart from the page
background, giving it a calm, structural presence. Active items and hover
states use --sidebar-accent, a subtle tinted fill that avoids heavy contrast.
Mint's 1.25rem radius carries through every interactive element for a
consistently rounded, airy look.
## Design tokens
--sidebar: oklch(0.985 0 0) / oklch(0.205 0 0) — sidebar background surface
--sidebar-foreground: (inherits --foreground chain) — default text in sidebar
--sidebar-primary: oklch(0.205 0 0) / oklch(0.488 0.243 264.376) — active item fill (blue in dark)
--sidebar-accent: (close to --accent) — hover / active item background
--sidebar-accent-foreground: (close to --accent-foreground) — text on hover / active
--sidebar-border: oklch(0.922 0 0) / oklch(1 0 0 / 10%) — internal dividers, rail, sub-menu border-l
--sidebar-ring: oklch(0.708 0 0) / oklch(0.439 0 0) — focus ring on sidebar interactive elements
--background: oklch(1 0 0) / oklch(0.145 0 0) — SidebarInset and SidebarInput surface
--ring: oklch(0.5 0.14 155) / oklch(0.62 0.15 155) — focus ring (non-sidebar elements)
## Sub-components
- SidebarProvider → context root; sets --sidebar-width CSS vars, wraps TooltipProvider, persists state to cookie
- Sidebar → desktop fixed panel or mobile Sheet depending on viewport
- SidebarTrigger → ghost icon Button (PanelLeftIcon) that calls toggleSidebar
- SidebarRail → invisible hover strip on sidebar edge; click toggles sidebar
- SidebarInset → <main> sibling to Sidebar; gains rounded-xl + shadow in "inset" variant
- SidebarInput → Input pre-styled for sidebar (bg-background, h-8, shadow-none)
- SidebarHeader → flex-col gap-2 p-2 slot at top of sidebar
- SidebarFooter → flex-col gap-2 p-2 slot at bottom of sidebar
- SidebarSeparator → Separator with bg-sidebar-border mx-2 for horizontal dividers
- SidebarContent → scrollable flex-1 area; overflow hidden in icon-collapse mode
- SidebarGroup → p-2 grouping wrapper with relative positioning
- SidebarGroupLabel → text-xs label (text-sidebar-foreground/70); collapses to opacity-0 in icon mode
- SidebarGroupAction → absolute icon button at group top-right; hidden in icon mode
- SidebarGroupContent → w-full text-sm wrapper for group body
- SidebarMenu → <ul> flex-col gap-1 nav list
- SidebarMenuItem → <li> with group/menu-item context for action/badge peer selectors
- SidebarMenuButton → primary interactive nav item; cva with variant + size; supports tooltip, sound, haptic
- SidebarMenuAction → absolute secondary icon button (e.g. +); showOnHover fades until hover/focus on md+
- SidebarMenuBadge → absolute numeric badge right of menu item; hidden in icon mode
- SidebarMenuSkeleton → loading placeholder; showIcon adds a 16px skeleton icon; width is deterministic via useId
- SidebarMenuSub → <ul> with border-l (border-sidebar-border) for nested items; hidden in icon mode
- SidebarMenuSubItem → <li> wrapper for sub-menu entries
- SidebarMenuSubButton → <a> or asChild sub-item; size sm|md; isActive highlights with sidebar-accent
## Variants
### SidebarMenuButton — variant
- default → transparent background; hover fills with --sidebar-accent. Standard nav item.
- outline → bg-background with 1px sidebar-border shadow ring; hover shifts ring to sidebar-accent. Use for elevated or call-out items.
### SidebarMenuButton — size
- sm → h-7 text-xs. Dense navigation lists.
- default → h-8 text-sm. Standard nav items.
- lg → h-12 text-sm. Large items such as user profile rows; collapses to p-0 in icon mode.
### Sidebar — variant
- sidebar → full-bleed to viewport edge with border-r/border-l divider.
- floating → p-2 with rounded-lg, border, and shadow-sm inner panel.
- inset → SidebarInset gains rounded-xl + shadow-sm; sidebar has no border.
### Sidebar — collapsible
- offcanvas → slides fully off-screen (translate by --sidebar-width). Default.
- icon → collapses to --sidebar-width-icon (3rem); shows icons only.
- none → always visible; no collapse behaviour.
## States
- hover: sidebar-accent background + sidebar-accent-foreground text on menu buttons and actions
- focus-visible: 2px ring in --sidebar-ring, outline-hidden
- active (click): sidebar-accent background + sidebar-accent-foreground text
- data-[active=true]: sidebar-accent fill + font-medium; set via isActive prop on SidebarMenuButton / SidebarMenuSubButton
- data-[state=open]: hover styles remain on open dropdown trigger
- disabled: pointer-events-none opacity-50 (both disabled and aria-disabled)
- collapsed (icon mode): SidebarGroupLabel, SidebarGroupAction, SidebarMenuBadge, SidebarMenuSub, SidebarMenuSubButton all hidden; SidebarMenuButton forced to size-8 p-2
## Conventions
- Always wrap the page in SidebarProvider with Sidebar and SidebarInset as siblings
- SidebarProvider persists open state to a "sidebar_state" cookie (7-day TTL); read it server-side for SSR-consistent initial state
- ⌘B / Ctrl+B toggles the sidebar globally via a window keydown listener registered in SidebarProvider
- Pass tooltip to SidebarMenuButton whenever the sidebar may collapse to icon mode — tooltips auto-suppress when expanded
- Use asChild on SidebarMenuButton / SidebarMenuSubButton to render a Next.js <Link> or <a> without an extra DOM node
- SidebarMenuAction showOnHover: pair with SidebarMenuButton in the same SidebarMenuItem — the peer selector drives visibility
- SidebarMenuSkeleton: render inside SidebarMenuItem list items to preserve consistent list structure during loading
- Sub-components expose data-slot="[name]" for CSS targeting (e.g. data-slot="sidebar-menu-button")
- sounds / haptic: SidebarMenuButton accepts sound (FeedbackType) and haptic (boolean) — GIN Design exclusives that play audio or trigger haptic feedback on click
- Do not nest SidebarMenuSub more than one level deep — the layout and icon-collapse logic is not designed for deeper nesting
- SidebarRail is optional but recommended for collapsible sidebars — it provides a drag-target affordance without visible UI chrome
## CSS vars (set by SidebarProvider)
--sidebar-width: 16rem
--sidebar-width-icon: 3rem
--sidebar-width-mobile: 18rem (applied inline on the mobile Sheet)Installation
$ npx shadcn add \https://gin-design.vercel.app/sidebar