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 · lightRegistry 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>
);
}