Skip to content
    Style: Wireframe

    Components · Navigation

    Sidebar navigation

    A docs sidebar: groups of links, the current page marked, collapsible groups on native details.

    38% of the 2,073 sites in The UI Index have a navigation (790 sites).

    Both previews render the same markup; the HTML tab below is that markup without a framework.

    Install

    React
    npx shadcn@latest add https://www.themes.dev/ui/r/sidebar-nav.json
    Astro
    npx shadcn@latest add https://www.themes.dev/ui/r/astro/sidebar-nav.json

    Or copy the files below into your project. They need clsx, tailwind-merge, and the themes.dev UI theme. The HTML needs only the compiled CSS.

    sidebar-nav.tsx
    import { cn } from '@/lib/utils';
    import { CHEVRON_PATH, sidebar, type SidebarGroup } from './sidebar-nav.variants';
    
    export type { SidebarGroup };
    
    export interface SidebarNavProps {
      groups: SidebarGroup[];
      /** The page you are on: its link gets aria-current=page. */
      currentPath?: string;
      label?: string;
      className?: string;
    }
    
    /** A docs sidebar: groups of links, the current page marked, collapsible groups on native details. */
    export function SidebarNav({ groups, currentPath, label = 'Docs', className }: SidebarNavProps) {
      return (
        <nav aria-label={label} className={cn(sidebar.root, className)}>
          {groups.map((g) => {
            const list = (
              <ul className={sidebar.list}>
                {g.links.map((l) => (
                  <li key={l.href}><a href={l.href} aria-current={l.href === currentPath ? 'page' : undefined} className={sidebar.link}>{l.label}</a></li>
                ))}
              </ul>
            );
            return g.collapsible ? (
              <details key={g.title} open={g.open} className={cn('group', sidebar.group)}>
                <summary className={sidebar.summary}>
                  {g.title}
                  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={sidebar.chevron}><path d={CHEVRON_PATH} /></svg>
                </summary>
                {list}
              </details>
            ) : (
              <div key={g.title} className={sidebar.group}>
                <p className={sidebar.heading}>{g.title}</p>
                {list}
              </div>
            );
          })}
        </nav>
      );
    }
    sidebar-nav.variants.ts
    /** A docs sidebar (ported from Flightdeck's docs-sidebar): groups of links under small headings, the current page
     *  marked with aria-current and a ground, collapsible groups on native details. */
    export const sidebar = {
      root: 'w-full max-w-64 text-sm',
      group: 'mb-6 last:mb-0',
      heading: 'mb-1 px-2 text-xs font-medium text-muted-foreground',
      summary:
        'mb-1 flex min-h-8 cursor-pointer list-none items-center justify-between rounded-md px-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed [&::-webkit-details-marker]:hidden',
      chevron: 'size-3.5 transition-transform duration-150 group-open:rotate-180',
      list: 'm-0 grid list-none gap-0.5 p-0',
      link: 'flex min-h-8 items-center rounded-md px-2 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:bg-accent aria-[current=page]:font-medium aria-[current=page]:text-foreground aria-[current=page]:hover:bg-input/30 aria-[current=page]:active:bg-pressed',
    };
    
    export type SidebarGroup = { title: string; links: { label: string; href: string }[]; collapsible?: boolean; open?: boolean };
    export const CHEVRON_PATH = 'M4 6l4 4 4-4';