Skip to content
All Taste components

Navigation

Sidebar navigation

An app sidebar with grouped sections, the current page marked, counts, and a collapsed icon rail.

expanded · icon rail · sections · counts · account footer

import { BarChart3, Calendar, ChevronsUpDown, CreditCard, FileText, Home, Settings, Users, type LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";

type Item = { href: string; label: string; icon: LucideIcon; count?: number };
type Section = { label: string; items: Item[] };

const sections: Section[] = [
  {
    label: "Workspace",
    items: [
      { href: "/", label: "Dashboard", icon: Home },
      { href: "/invoices", label: "Invoices", icon: FileText, count: 12 },
      { href: "/customers", label: "Customers", icon: Users },
      { href: "/bookings", label: "Bookings", icon: Calendar, count: 3 },
      { href: "/reports", label: "Reports", icon: BarChart3 },
    ],
  },
  {
    label: "Account",
    items: [
      { href: "/billing", label: "Billing", icon: CreditCard },
      { href: "/settings", label: "Settings", icon: Settings },
    ],
  },
];

export function Sidebar({ current, collapsed = false }: { current: string; collapsed?: boolean }) {
  return (
    <nav
      aria-label="Workspace"
      className={cn(
        "flex min-h-full flex-col gap-4 rounded-lg border border-border bg-surface",
        collapsed ? "w-14 items-center px-2 py-3" : "w-[232px] p-3",
      )}
    >
      {collapsed ? (
        <span className="mb-3 grid size-7 place-items-center rounded-sm bg-primary text-xs font-semibold text-primary-fg">K</span>
      ) : (
        <button type="button" className="flex w-full items-center gap-2 rounded p-1.5 text-left text-[13.5px] font-medium text-fg hover:bg-surface-2">
          <span className="grid size-[22px] place-items-center rounded-sm bg-primary text-xs font-semibold text-primary-fg">K</span>
          Kaveri Studio
          <ChevronsUpDown className="ml-auto size-3.5 text-fg-subtle" aria-hidden />
        </button>
      )}

      {sections.map((section) => (
        <div key={section.label} className="grid gap-0.5">
          {!collapsed && <p className="mb-1 px-2 text-[11.5px] font-medium text-fg-subtle">{section.label}</p>}
          {section.items.map(({ href, label, icon: Icon, count }) => (
            <a
              key={href}
              href={href}
              title={collapsed ? label : undefined}
              aria-label={collapsed ? (count ? label + ", " + count : label) : undefined}
              aria-current={current === href ? "page" : undefined}
              className={cn(
                "group relative flex h-8 items-center gap-2.5 rounded px-2 text-[13.5px] font-medium text-fg-muted transition",
                "hover:bg-surface-2 hover:text-fg",
                "aria-[current=page]:bg-bg aria-[current=page]:text-fg aria-[current=page]:shadow-sm aria-[current=page]:ring-1 aria-[current=page]:ring-border",
                collapsed && "size-9 justify-center px-0",
              )}
            >
              <Icon className="size-4 shrink-0" aria-hidden />
              {!collapsed && label}
              {count !== undefined && (
                <span
                  className={cn(
                    "grid place-items-center rounded-full font-medium tabular-nums",
                    collapsed
                      ? "absolute -right-px top-px h-4 min-w-4 bg-primary px-1 text-[10px] text-primary-fg ring-2 ring-surface"
                      : "ml-auto h-[18px] min-w-5 bg-surface-2 px-1.5 text-[11.5px] text-fg-muted group-aria-[current=page]:bg-primary-soft group-aria-[current=page]:text-primary-soft-fg",
                  )}
                >
                  {count}
                </span>
              )}
            </a>
          ))}
        </div>
      ))}
    </nav>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.