# Sidebar navigation (Caseroom component)

A docs sidebar: groups of links, the current page marked, collapsible groups on native details. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

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

## Install

- React: `npx shadcn@latest add https://www.themes.dev/caseroom/r/sidebar-nav.json`
- Astro: `npx shadcn@latest add https://www.themes.dev/caseroom/r/astro/sidebar-nav.json`
- HTML: https://www.themes.dev/caseroom/r/html/sidebar-nav.html (with https://www.themes.dev/caseroom/r/html/caseroom.css)

Page: https://www.themes.dev/caseroom/components/sidebar-nav/ · Rules for agents: https://www.themes.dev/caseroom/AGENT.md

## Code

### sidebar-nav.astro

```astro
---
import { cn } from '@/lib/utils';
import { CHEVRON_PATH, sidebar, type SidebarGroup } from './sidebar-nav.variants';

interface Props {
  groups: SidebarGroup[];
  /** The page you are on: its link gets aria-current=page. */
  currentPath?: string;
  label?: string;
  class?: string;
}

const { groups, currentPath, label = 'Docs', class: className } = Astro.props;
---

<nav aria-label={label} class={cn(sidebar.root, className)}>
  {
    groups.map((g) => {
      const list = (
        <ul class={sidebar.list}>
          {g.links.map((l) => (
            <li><a href={l.href} aria-current={l.href === currentPath ? 'page' : undefined} class={sidebar.link}>{l.label}</a></li>
          ))}
        </ul>
      );
      return g.collapsible ? (
        <details open={g.open} class={cn('group', sidebar.group)}>
          <summary class={sidebar.summary}>
            {g.title}
            <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class={sidebar.chevron}><path d={CHEVRON_PATH} /></svg>
          </summary>
          {list}
        </details>
      ) : (
        <div class={sidebar.group}>
          <p class={sidebar.heading}>{g.title}</p>
          {list}
        </div>
      );
    })
  }
</nav>
```

### sidebar-nav.tsx

```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

```tsx
/** 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';
```
