# Workspace Sidebar

> A compact workspace sidebar with grouped navigation, active links, Keyline icons, and organization settings.

A shadcn/ui block from the [@studio-blocks](https://studio-blocks.arhamkhnz.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/studio-blocks/sidebar-01).

- Page, with a live preview: https://shadcn.exchange/r/studio-blocks/sidebar-01
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/studio-blocks/sidebar-01-light-798b56f477d8ef0c.png
- Registry item: https://studio-blocks.arhamkhnz.com/r/sidebar-01.json
- Categories: navigation
- Listed: 2026-10-24, source last changed 2026-10-24

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/studio-blocks/sidebar-01.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@keyline-icons/react`, `cn`
- Registry items: `sidebar`

## Files

### `src/registry/blocks/navigation/sidebar-01/sidebar-01.tsx`

```tsx
"use client";

import { useState, type ComponentProps, type ComponentType, type MouseEvent } from "react";
import {
  ActivityIcon,
  BanknoteIcon,
  BuildingsIcon,
  ChartColumnIcon,
  FileIcon,
  HeadsetIcon,
  ReceiptIcon,
  SettingsIcon,
  UsersIcon,
  type IconProps,
} from "@keyline-icons/react";
import { cn } from "cn";

import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarSeparator,
  SidebarTrigger,
  useSidebar,
} from "@/components/ui/sidebar";

const groups = [
  {
    label: "Workspace",
    items: [
      { title: "Overview", href: "/dashboard", icon: BuildingsIcon, exact: true },
      { title: "Contracts", href: "/dashboard/contracts", icon: FileIcon },
      { title: "People", href: "/dashboard/people", icon: UsersIcon },
    ],
  },
  {
    label: "Finance",
    items: [
      { title: "Payroll", href: "/dashboard/payroll", icon: BanknoteIcon },
      { title: "Invoices", href: "/dashboard/invoices", icon: ReceiptIcon },
    ],
  },
  {
    label: "Manage",
    items: [
      { title: "Reports", href: "/dashboard/reports", icon: ChartColumnIcon },
      { title: "Activity", href: "/dashboard/activity", icon: ActivityIcon },
    ],
  },
];

const footerItems = [
  { title: "Help & support", href: "/dashboard/support", icon: HeadsetIcon },
  { title: "Organization", href: "/dashboard/settings/organization", icon: SettingsIcon },
];

type NavigationItem = {
  title: string;
  href: string;
  icon: ComponentType<IconProps>;
  exact?: boolean;
};

export type AppSidebarProps = ComponentProps<typeof Sidebar> & {
  activePath?: string;
  onNavigate?: (href: string) => void;
};

export function AppSidebar({ activePath = "/dashboard", onNavigate, className, ...props }: AppSidebarProps) {
  const { setOpenMobile } = useSidebar();

  function navigate(event: MouseEvent<HTMLAnchorElement>, href: string) {
    if (onNavigate && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) {
      event.preventDefault();
      onNavigate(href);
    }
    setOpenMobile(false);
  }

  function renderItem(item: NavigationItem) {
    const Icon = item.icon;
    const active =
      activePath === item.href ||
      activePath === `${item.href}/` ||
      (!item.exact && activePath.startsWith(`${item.href}/`));

    return (
      <SidebarMenuItem key={item.href}>
        <SidebarMenuButton
          isActive={active}
          className="gap-1.5"
          render={
            <a
              href={item.href}
              aria-current={active ? "page" : undefined}
              onClick={(event) => navigate(event, item.href)}
            />
          }
        >
          <Icon aria-hidden="true" strokeWidth={1.75} />
          <span className="font-[450]">{item.title}</span>
        </SidebarMenuButton>
      </SidebarMenuItem>
    );
  }

  return (
    <Sidebar collapsible="icon" className={cn("h-full", className)} {...props}>
      <SidebarHeader className="flex-row items-center gap-0">
        <SidebarMenu className="min-w-0 flex-1 group-data-[collapsible=icon]:hidden">
          <SidebarMenuItem>
            <SidebarMenuButton
              className="gap-2"
              render={<a href="/dashboard" onClick={(event) => navigate(event, "/dashboard")} />}
            >
              <span
                aria-hidden="true"
                className="grid size-5 shrink-0 place-items-center rounded-sm bg-sidebar-primary font-mono text-xs text-sidebar-primary-foreground"
              >
                A
              </span>
              <span className="font-[450]">Acme Studio</span>
            </SidebarMenuButton>
          </SidebarMenuItem>
        </SidebarMenu>
        <SidebarTrigger className="shrink-0" />
      </SidebarHeader>
      <SidebarContent>
        <nav aria-label="Main navigation">
          {groups.map((group) => (
            <SidebarGroup key={group.label} className="py-1 first:pt-0">
              <SidebarGroupLabel className="font-mono font-normal uppercase">{group.label}</SidebarGroupLabel>
              <SidebarGroupContent>
                <SidebarMenu className="gap-px">{group.items.map(renderItem)}</SidebarMenu>
              </SidebarGroupContent>
            </SidebarGroup>
          ))}
        </nav>
      </SidebarContent>
      <SidebarFooter>
        <SidebarSeparator className="mx-0" />
        <SidebarMenu className="gap-px">{footerItems.map(renderItem)}</SidebarMenu>
      </SidebarFooter>
    </Sidebar>
  );
}

// Standalone preview example. In your app's layout, wrap <AppSidebar /> and
// {children} with <SidebarProvider> to share collapse state and mobile toggling.
export function Sidebar01() {
  const [activePath, setActivePath] = useState("/dashboard");

  return (
    <SidebarProvider className="relative mx-auto h-180 min-h-0 w-fit max-w-full overflow-hidden rounded-xl border contain-[layout_paint]">
      <AppSidebar activePath={activePath} onNavigate={setActivePath} />
      <SidebarTrigger className="m-2 md:hidden" />
    </SidebarProvider>
  );
}
```
