shadimage
fluid
blocksidebar-appsource: ok · hit

App Sidebar

A complete app shell: workspace header with the peek cross-fade, search on the menu rows' rhythm with a hover-revealed shortcut chip, collapsible sections with label actions and badges, a user footer, and an inset topbar whose trigger fades back in at its settled position after a pin.

npx shadcn add "https://www.fluidfunctionalism.com/r/sidebar-app.json"
free

Where it goes

navbarfootersidebardashboard-shell

Interaction

scroll-revealaccordiondropdown

Use cases

dashboard

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light

Registry dependencies

utilshttps://www.fluidfunctionalism.com/r/icon-context.jsonhttps://www.fluidfunctionalism.com/r/size-context.jsonhttps://www.fluidfunctionalism.com/r/sidebar.jsonhttps://www.fluidfunctionalism.com/r/tooltip.jsonhttps://www.fluidfunctionalism.com/r/dropdown.jsonhttps://www.fluidfunctionalism.com/r/sidebar-workspace-header.jsonhttps://www.fluidfunctionalism.com/r/sidebar-user-footer.jsonhttps://www.fluidfunctionalism.com/r/sidebar-search-field.jsonhttps://www.fluidfunctionalism.com/r/sidebar-inset-topbar.jsonhttps://www.fluidfunctionalism.com/r/type-scale.json

Files3 · source cached 10/8/2026, 2:10:22 PM

registry/blocks/sidebar-app/app-sidebar.tsx
"use client";

import { useState } from "react";
import {
  Sidebar,
  SidebarHeader,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarGroupActions,
  SidebarGroupAction,
  SidebarMenu,
  SidebarMenuItem,
  SidebarMenuButton,
  SidebarMenuBadge,
  type SidebarProps,
} from "@/components/ui/sidebar";
import { Tooltip } from "@/components/ui/tooltip";
import { MenuItem } from "@/components/ui/menu-item";
import { useIcons } from "@/lib/icon-context";
import {
  SidebarWorkspaceHeader,
  WorkspaceTile,
} from "@/components/sidebar-app/workspace-header";
import { SidebarUserFooter } from "@/components/sidebar-app/user-footer";
import { SidebarSearchField } from "@/components/sidebar-app/search-field";
import { NAV_SECTIONS } from "@/components/sidebar-app/nav-data";

// ---------------------------------------------------------------------------
// A complete app sidebar assembled from the workspace-header, search-field,
// and user-footer blocks: brand row with the peek cross-fade, search on the
// rows' rhythm, collapsible sections with label actions, badge-carrying
// rows, and the identity row anchoring the outer edge. Swap the seed data
// in nav-data.ts and the names below for your own.
// ---------------------------------------------------------------------------

export function AppSidebar(props: Omit<SidebarProps, "children">) {
  const [active, setActive] = useState("Home");
  const icons = useIcons();
  const PlusIcon = icons.plus;
  const UserIcon = icons.user;
  const SettingsIcon = icons.settings;
  const ArrowLeftIcon = icons["arrow-left"];

  return (
    <Sidebar variant="inset" {...props}>
      <SidebarHeader>
        <SidebarWorkspaceHeader
          name="Acme Inc"
          tile={<WorkspaceTile>A</WorkspaceTile>}
          checkedIndex={0}
          menu={
            <>
              <MenuItem index={0} label="Acme Inc" checked onSelect={() => {}} />
              <MenuItem index={1} label="Personal" onSelect={() => {}} />
              <MenuItem index={2} icon={PlusIcon} label="New workspace" onSelect={() => {}} />
            </>
          }
        />
        {/* Search + action rows are ONE block: the field reads as the list's
            first row, on the menu rows' own tight rhythm. */}
        <div className="flex flex-col">
          <SidebarSearchField />
          <SidebarMenu>
            <SidebarMenuItem>
              <SidebarMenuButton icon={PlusIcon}>
                New
                {/* shortcut chip, revealed on row hover */}
                <span className="ml-auto inline-flex opacity-0 transition-opacity duration-80 group-hover/menu-item:opacity-100 group-focus-within/menu-item:opacity-100">
                  <kbd className="font-sans text-[length:var(--fs-micro,11px)] leading-[var(--lh-micro,14px)] text-muted-foreground">
                    ⇧⌘O
                  </kbd>
                </span>
              </SidebarMenuButton>
            </SidebarMenuItem>
          </SidebarMenu>
        </div>
      </SidebarHeader>

      <SidebarContent>
        {NAV_SECTIONS.map((section) => (
          <SidebarGroup key={section.label} collapsible>
            <SidebarGroupLabel>{section.label}</SidebarGroupLabel>
            <SidebarGroupActions>
              <Tooltip content="Add item" side="top">
                <SidebarGroupAction aria-label="Add item">
                  <PlusIcon />
                </SidebarGroupAction>
              </Tooltip>
            </SidebarGroupActions>
            <SidebarMenu>
              {section.items.map((item) => (
                <SidebarMenuItem key={item.label}>
                  <SidebarMenuButton
                    icon={icons[item.icon]}
                    isActive={item.label === active}
                    onClick={() => setActive(item.label)}
                  >
                    {item.label}
                  </SidebarMenuButton>
                  {item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}
                </SidebarMenuItem>
              ))}
            </SidebarMenu>
          </SidebarGroup>
        ))}
      </SidebarContent>

      <SidebarFooter>
        <SidebarUserFooter
          name="Jane Doe"
          avatar={
            <span className="flex size-5 items-center justify-center rounded-full bg-muted-foreground text-[length:var(--fs-micro-compact,10px)] leading-[var(--lh-micro-compact,12px)] text-background">
              J
            </span>
          }
          menu={
            <>
              <MenuItem index={0} icon={UserIcon} label="Profile" onSelect={() => {}} />
              <MenuItem index={1} icon={SettingsIcon} label="Settings" onSelect={() => {}} />
              <MenuItem index={2} icon={ArrowLeftIcon} label="Log out" onSelect={() => {}} />
            </>
          }
        />
      </SidebarFooter>
    </Sidebar>
  );
}
registry/blocks/sidebar-app/nav-data.ts
import { type IconName } from "@/lib/icon-context";

// ---------------------------------------------------------------------------
// Seed navigation data — replace with your own. Icons are keys into the
// icon context (lucide by default; override via IconProvider).
// ---------------------------------------------------------------------------

export interface NavItem {
  label: string;
  icon: IconName;
  badge?: string;
}

export interface NavSection {
  label: string;
  items: NavItem[];
}

export const NAV_SECTIONS: NavSection[] = [
  {
    label: "Platform",
    items: [
      { label: "Home", icon: "star" },
      { label: "Inbox", icon: "mail", badge: "12" },
      { label: "Calendar", icon: "clock" },
    ],
  },
  {
    label: "Workspace",
    items: [
      { label: "Notifications", icon: "bell", badge: "3" },
      { label: "Members", icon: "users" },
      { label: "Settings", icon: "settings" },
    ],
  },
];
registry/blocks/sidebar-app/page.tsx
"use client";

import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/sidebar-app/app-sidebar";
import { SidebarInsetTopbar } from "@/components/sidebar-app/inset-topbar";

// ---------------------------------------------------------------------------
// Paste-and-run app shell. peek="hover" floats the collapsed rail back out
// the moment the cursor reaches its edge or the trigger; pinning it open
// from that peek is seamless. State persists to the "sidebar_state" cookie —
// read it in a server layout for a flicker-free default:
//
//   const defaultOpen = (await cookies()).get("sidebar_state")?.value !== "false";
//   <SidebarProvider defaultOpen={defaultOpen}>…
// ---------------------------------------------------------------------------

export default function Page() {
  return (
    <SidebarProvider peek="hover">
      <AppSidebar />
      <SidebarInset>
        <SidebarInsetTopbar />
        <main className="flex flex-1 flex-col gap-4 p-4">
          {/* your page */}
        </main>
      </SidebarInset>
    </SidebarProvider>
  );
}

More from fluid

Browse all components in this library →