AppShell
MD · 1950B gzip budgetResponsive app layout with sidebar, header, and main content areas.
- Category
- Navigation
- Budget
- md, several states or a live subscription
- External runtime
- React / React DOM only
axe fixtureSSR rendersource scanbundle budget
Preview
Live fixtureMain content
Try the component here. Locale-aware formatting and direction follow the selected language; example content and labels are not automatically translated. Overlay demos use interactive launchers.
Install
npx shadcn@latest add https://gear5-ui.vercel.app/r/app-shell.jsonCopies the source into your project. Pulls in 1 shared registry item: cn.
New here? Set up Tailwind and import aliases first →Source & props
Edit on GitHub ↗Prop types and inline documentation are included below. This is repository source; the installer rewrites shared imports for your project.
"use client";
import { useState } from "react";
import { cn } from "../lib/cn";
export interface AppShellProps {
sidebar: React.ReactNode;
header: React.ReactNode;
children: React.ReactNode;
className?: string;
}
/**
* App shell layout with a collapsible sidebar, header, and main content area.
* The sidebar toggle uses `aria-expanded` and the sidebar has
* `aria-label="Sidebar"` for screen readers.
*/
export function AppShell({ sidebar, header, children, className }: AppShellProps) {
const [sidebarOpen, setSidebarOpen] = useState(true);
return (
<div className={cn("flex h-screen overflow-hidden bg-white dark:bg-neutral-950", className)}>
{/* Sidebar */}
<aside
aria-label="Sidebar"
className={cn(
"flex flex-col border-e border-neutral-200 bg-neutral-50 transition-[width] motion-reduce:transition-none dark:border-neutral-800 dark:bg-neutral-900",
sidebarOpen ? "w-64" : "w-0 overflow-hidden",
)}
>
<div className="flex-1 overflow-y-auto p-4">{sidebar}</div>
</aside>
<div className="flex flex-1 flex-col overflow-hidden">
{/* Header */}
<div className="flex items-center gap-2 border-b border-neutral-200 px-4 py-3 dark:border-neutral-800">
<button
type="button"
aria-label={sidebarOpen ? "Collapse sidebar" : "Expand sidebar"}
aria-expanded={sidebarOpen}
onClick={() => setSidebarOpen((prev) => !prev)}
className="rounded-md p-1.5 text-neutral-600 hover:bg-neutral-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:text-neutral-400 dark:hover:bg-neutral-800"
>
<span aria-hidden="true" className="text-lg">
{sidebarOpen ? "◂" : "☰"}
</span>
</button>
<div className="flex-1">{header}</div>
</div>
{/* Main content */}
<main className="flex-1 overflow-y-auto p-6">{children}</main>
</div>
</div>
);
}
What CI checks
Quality contract- Bundled, minified and gzipped against its tier budget
- Audited by axe in the state previewed above
- Rendered through react-dom/server with no browser globals
- Scanned for network calls, dangerous sinks, and unguarded animation