Skip to content

Sidebar Inset

Inset app shell: the sidebar sits on the tinted page background while content lives in a raised rounded panel. Workspace header, collapsible nested sections with guide line, support/settings and user card; drawer on mobile.

minimal/layout/sidebar-inset
Open ↗

Source

"use client";

import { useEffect, useId, useState, type ReactNode } from "react";
import { Activity, BellRing, CalendarClock, ChevronDown, FileText, Home, LifeBuoy, Menu, Radio, Settings, X, type LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";

export interface SidebarInsetSection {
  label: string;
  icon: LucideIcon;
  href?: string;
  /** Sub-links make the section collapsible. */
  children?: { label: string; href: string }[];
}

export interface SidebarInsetProps {
  workspace?: string;
  sections?: SidebarInsetSection[];
  activeHref?: string;
  user?: { name: string; email: string; initials: string };
  children?: ReactNode;
  className?: string;
}

const DEFAULT_SECTIONS: SidebarInsetSection[] = [
  { label: "Home", icon: Home, href: "#home" },
  {
    label: "Incidents",
    icon: Activity,
    children: [
      { label: "Active", href: "#incidents-active" },
      { label: "Resolved", href: "#incidents-resolved" },
      { label: "Postmortems", href: "#postmortems" },
    ],
  },
  {
    label: "On-call",
    icon: CalendarClock,
    children: [
      { label: "Schedules", href: "#schedules" },
      { label: "Escalation policies", href: "#policies" },
      { label: "Overrides", href: "#overrides" },
    ],
  },
  { label: "Alert rules", icon: BellRing, href: "#alerts" },
  { label: "Status pages", icon: Radio, href: "#status" },
  { label: "Reports", icon: FileText, href: "#reports" },
];

function Section({ s, activeHref, onNavigate }: { s: SidebarInsetSection; activeHref: string; onNavigate: () => void }) {
  const id = useId();
  const containsActive = s.children?.some((c) => c.href === activeHref) ?? false;
  const [open, setOpen] = useState(containsActive);
  const row = "da-focus da-transition flex h-8 w-full items-center gap-2.5 rounded-da-md px-2 text-[13px]";

  if (!s.children) {
    const active = s.href === activeHref;
    return (
      <a
        href={s.href}
        onClick={onNavigate}
        aria-current={active ? "page" : undefined}
        className={cn(row, active ? "bg-da-surface font-medium text-da-fg shadow-da-sm" : "text-da-muted-fg hover:bg-da-fg/5 hover:text-da-fg")}
      >
        <s.icon aria-hidden className="size-4" />
        {s.label}
      </a>
    );
  }
  return (
    <div>
      <button
        type="button"
        aria-expanded={open}
        aria-controls={id}
        onClick={() => setOpen((o) => !o)}
        className={cn(row, "text-da-muted-fg hover:bg-da-fg/5 hover:text-da-fg", containsActive && "text-da-fg")}
      >
        <s.icon aria-hidden className="size-4" />
        {s.label}
        <ChevronDown aria-hidden className={cn("da-transition ml-auto size-3.5", !open && "-rotate-90")} />
      </button>
      <ul id={id} hidden={!open} className="relative mt-0.5 mb-1 ml-4 border-l border-da-border pl-2">
        {s.children.map((c) => {
          const active = c.href === activeHref;
          return (
            <li key={c.href}>
              <a
                href={c.href}
                onClick={onNavigate}
                aria-current={active ? "page" : undefined}
                className={cn(
                  "da-focus da-transition block rounded-da-sm px-2 py-1.5 text-[13px]",
                  active ? "bg-da-surface font-medium text-da-fg shadow-da-sm" : "text-da-muted-fg hover:text-da-fg",
                )}
              >
                {c.label}
              </a>
            </li>
          );
        })}
      </ul>
    </div>
  );
}

/** Inset app shell: sidebar sits on the tinted page background, content is a raised rounded panel. Collapsible nested sections, user card, mobile drawer. */
export function SidebarInset({
  workspace = "Acme Inc.",
  sections = DEFAULT_SECTIONS,
  activeHref = "#incidents-active",
  user = { name: "Maya Chen", email: "maya@acme.io", initials: "MC" },
  children,
  className,
}: SidebarInsetProps) {
  const [open, setOpen] = useState(false);
  const navId = useId();

  useEffect(() => {
    if (!open) return;
    const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false);
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open]);

  return (
    <div className={cn("flex min-h-dvh bg-da-surface-2 text-da-fg", className)}>
      {open && <div aria-hidden className="fixed inset-0 z-40 bg-da-overlay lg:hidden" onClick={() => setOpen(false)} />}
      <aside
        id={navId}
        className={cn(
          "fixed inset-y-0 left-0 z-50 flex w-60 flex-col bg-da-surface-2 p-3 transition-transform duration-(--da-duration-slow) ease-da lg:sticky lg:top-0 lg:h-dvh lg:translate-x-0 motion-reduce:transition-none",
          open ? "translate-x-0 shadow-da-lg" : "-translate-x-full",
        )}
      >
        <div className="flex items-center gap-2 px-2 py-1.5">
          <span aria-hidden className="grid size-6 place-items-center rounded-da-sm bg-da-primary text-[11px] font-bold text-da-primary-fg">
            {workspace[0]}
          </span>
          <span className="text-[13px] font-semibold">{workspace}</span>
          <button
            type="button"
            onClick={() => setOpen(false)}
            aria-label="Close navigation"
            className="da-focus ml-auto grid size-7 place-items-center rounded-da-sm text-da-muted-fg lg:hidden"
          >
            <X aria-hidden className="size-4" />
          </button>
        </div>
        <nav aria-label="Main" className="mt-4 flex-1 space-y-0.5 overflow-y-auto">
          {sections.map((s) => (
            <Section key={s.label} s={s} activeHref={activeHref} onNavigate={() => setOpen(false)} />
          ))}
        </nav>
        <div className="space-y-0.5 border-t border-da-border pt-2">
          <a href="#support" className="da-focus da-transition flex h-8 items-center gap-2.5 rounded-da-md px-2 text-[13px] text-da-muted-fg hover:text-da-fg">
            <LifeBuoy aria-hidden className="size-4" /> Support
          </a>
          <a href="#settings" className="da-focus da-transition flex h-8 items-center gap-2.5 rounded-da-md px-2 text-[13px] text-da-muted-fg hover:text-da-fg">
            <Settings aria-hidden className="size-4" /> Settings
          </a>
          <div className="mt-2 flex items-center gap-2.5 rounded-da-md p-2">
            <span aria-hidden className="grid size-8 place-items-center rounded-full bg-da-accent text-[11px] font-medium text-da-accent-fg">
              {user.initials}
            </span>
            <span className="min-w-0 text-[12px] leading-tight">
              <span className="block truncate font-medium">{user.name}</span>
              <span className="block truncate text-da-muted-fg">{user.email}</span>
            </span>
          </div>
        </div>
      </aside>
      <div className="flex min-w-0 flex-1 flex-col lg:py-2 lg:pr-2">
        <div className="flex items-center gap-2 border-b border-da-border bg-da-bg px-4 py-2.5 lg:hidden">
          <button
            type="button"
            onClick={() => setOpen(true)}
            aria-expanded={open}
            aria-controls={navId}
            aria-label="Open navigation"
            className="da-focus grid size-8 place-items-center rounded-da-md text-da-muted-fg hover:bg-da-muted"
          >
            <Menu aria-hidden className="size-4" />
          </button>
          <span className="text-[13px] font-semibold">{workspace}</span>
        </div>
        <main className="min-w-0 flex-1 bg-da-bg lg:da-stroke lg:rounded-da-lg lg:shadow-da-sm">{children}</main>
      </div>
    </div>
  );
}

export default SidebarInset;

modules/minimal/layout/sidebar-inset/index.tsx

Props

PropTypeDefaultDescription
workspacestring"Acme Inc."Workspace name.
sections{ label, icon, href?, children? }[]—Nav; children make a collapsible section.
activeHrefstring—Current page (aria-current).
user{ name, email, initials }—User card.
childrenReactNode—Page content (main).
classNamestring—Classes on the root.

Other sidebar variants in Minimal

Sidebar in other art directions

App Sidebar

App shell with a sticky left sidebar: workspace header, grouped nav with yellow active state and counters, usage meter, user footer. Becomes a top bar + slide-over drawer below lg.

BrutalistSidebar

Icon Rail

Compact 80px icon rail: ink brand square, square icon links with yellow active state, red counters and ink tooltips on hover/focus, settings pinned at the bottom. Server-safe, CSS-only tooltips.

BrutalistSidebar

Sidebar Collapsible

App sidebar that collapses from 256px to a 76px icon column with a stepped width animation; ink active item, labels become sr-only when collapsed, collapse toggle pinned at the bottom.

BrutalistSidebar

Sidebar Dual

Two-level navigation: an ink icon rail switches sections, and a paper second panel lists that section's links with counts and a yellow active link. Second panel hides on very small screens.

BrutalistSidebar

App Sidebar

Floating frosted sidebar inset from the viewport edges: gradient-orb brand, pill nav items with an ink active state and gradient badges, workspace group, gradient promo card and user chip. Becomes a floating pill top bar + glass drawer below lg.

GlassSidebar

Sidebar Adaptive

Adaptive navigation: a full glass sidebar with brand, gradient action button, labelled links and a usage meter on desktop; a floating bottom tab bar with a raised gradient “+” on mobile.

GlassSidebar

Sidebar Channels

Chat-app style double sidebar: a column of gradient workspace squircles plus a glass channel panel with collapsible sections, private/live icons, unread counts and LIVE meeting badges; drawer on mobile.

GlassSidebar

Sidebar Floating

Floating glass icon rail detached from the viewport edges: gradient logo orb, gradient active item, unread badges, glass tooltips on hover/focus, settings and avatar at the bottom; a floating bottom bar on mobile.

GlassSidebar

App Sidebar

Typographic app sidebar: hairline right border, logo + site switcher, mono group labels, plain text links with right-aligned mono counts; the active link is ink with a signal dot; drawer on mobile.

Mono CleanSidebar

Sidebar Icons

Narrow icon rail (56px) of square hairline cells; the active cell is filled ink; labels appear as mono tooltips to the right on hover/focus; turns into a bottom bar on mobile.

Mono CleanSidebar

Sidebar Ink

Ink sidebar (stays near-black in dark mode): giant numbered text links “01 Overview”, the active one full white with an arrow, a storage meter as a hairline bar at the bottom; drawer on mobile.

Mono CleanSidebar

Sidebar Tree

CMS page tree: nested pages drawn with hairline guide lines and +/− toggles (aria-expanded), mono “Pages” header with an add button; active page ink with signal dot; drawer on mobile.

Mono CleanSidebar

App Sidebar

Classic enterprise app shell: white bordered sidebar with brand, entity switcher, grouped nav (uppercase group labels, count badges, blue active state), settings and user footer; off-canvas drawer on mobile.

Neo CorporateSidebar

Sidebar Collapsible

Sidebar that collapses from 240px to a 64px icon rail with a toggle button (aria-expanded); collapsed items keep tooltips (title) and sr-only labels. Always a rail on small screens.

Neo CorporateSidebar

Sidebar Dual

Two-level navigation: a narrow icon rail of top-level sections (tooltips, aria-pressed) and a secondary panel listing the selected section’s pages; the panel hides below md.

Neo CorporateSidebar

Sidebar Navy

Dark navy sidebar (fg-colored in light mode, surface in dark mode — dark in both): brand, search field, nav with white-tint active state and badges, and a cash-balance card at the bottom; drawer on mobile.

Neo CorporateSidebar

App Sidebar

Calm app shell: cream sidebar with leaf logo, pill nav items (sage pill for the active page), a small “this year” footprint card with a sprout, and settings at the bottom; drawer on mobile.

Organic SoftSidebar

Sidebar Floating

Floating sidebar: a rounded cream panel inset from the page edges with a soft shadow, italic serif group labels, a sage “New entry” pill and sage-tinted active items; drawer on mobile.

Organic SoftSidebar

Sidebar Garden

Deep sage sidebar (dark sage tint in dark mode) with cream text, a translucent active pill, count bubbles, rolling hills illustrated along the bottom and the user’s name on the hill; drawer on mobile.

Organic SoftSidebar

Sidebar Rail

Slim round icon rail: circular icon links on a sand column, the active one a sage circle with a leaf-shaped marker; labels appear as soft tooltips on hover/focus; becomes a bottom tab bar on mobile.

Organic SoftSidebar

App Sidebar

Friendly app shell: warm sidebar with emoji workspace chip, big periwinkle “New task” pill, search field, nav with peach count bubbles and a colored-dot board list; slide-in drawer on mobile.

Soft FlatSidebar

Sidebar Boards

Board navigator: starred favorites, then collapsible emoji spaces holding emoji boards with count bubbles; active board in a white pill; dashed “New space” button.

Soft FlatSidebar

Sidebar Card

Floating white sidebar card with generous radius on a tinted page: two-dot logo, nav with lavender active pill, butter “tip of the day” card and user row; horizontal nav on mobile.

Soft FlatSidebar

Sidebar Rail

Slim rounded icon rail: round icons whose active item fills with its own pastel color, dark pill tooltips, emoji avatar at the bottom; floating bottom bar on mobile.

Soft FlatSidebar