Siteplane UI

Application shell

Navigate a responsive workspace with a sidebar, header, and content region.

Markdown
"use client";

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/siteplane/components/card";
import {
  DashboardShell,
  DashboardShellHeader,
  DashboardShellInset,
  DashboardShellMain,
  DashboardShellPrimary,
  DashboardShellSidebar,
  DashboardShellSidebarHeader,
  DashboardShellSidebarTrigger,
} from "@/siteplane/components/dashboard-shell";
import {
  SettingsList,
  SettingsListItem,
} from "@/siteplane/components/settings-list";
import {
  SidebarBrand,
  SidebarContent,
  SidebarGroup,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from "@/siteplane/components/sidebar";
import { Switch } from "@/siteplane/components/switch";
import { FolderIcon, HomeIcon, SettingsIcon } from "lucide-react";
import { useState } from "react";

const navigation = [
  { label: "Overview", icon: HomeIcon },
  { label: "Projects", icon: FolderIcon },
  { label: "Settings", icon: SettingsIcon },
] as const;

type Page = (typeof navigation)[number]["label"];

const pages = {
  Overview: {
    title: "Recent activity",
    description: "Updates from your projects this week.",
    items: [
      "Website redesign moved to review",
      "Ben Carter joined Mobile application",
      "Sofia Nguyen shared Customer research",
    ],
  },
  Projects: {
    title: "Active projects",
    description: "Projects your team is working on.",
    items: ["Website redesign", "Mobile application", "Customer research"],
  },
};

export default function Example() {
  const [active, setActive] = useState<Page>("Overview");
  const [notifications, setNotifications] = useState(true);
  return (
    <div className="relative h-[32rem] w-full overflow-hidden [transform:translateZ(0)]">
      <DashboardShell className="h-full min-h-0">
        <DashboardShellSidebar className="h-full" collapsible="offcanvas">
          <DashboardShellSidebarHeader>
            <SidebarBrand>Acme</SidebarBrand>
          </DashboardShellSidebarHeader>
          <SidebarContent>
            <SidebarGroup>
              <SidebarMenu>
                {navigation.map(({ label, icon: Icon }) => (
                  <SidebarMenuItem key={label}>
                    <SidebarMenuButton
                      active={active === label}
                      onClick={() => setActive(label)}
                    >
                      <Icon />
                      <span>{label}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroup>
          </SidebarContent>
        </DashboardShellSidebar>
        <DashboardShellInset>
          <DashboardShellHeader>
            <DashboardShellSidebarTrigger />
            <h1 className="font-semibold">{active}</h1>
          </DashboardShellHeader>
          <DashboardShellPrimary>
            <DashboardShellMain>
              {active === "Settings" ? (
                <SettingsList
                  title="Notifications"
                  description="Choose which updates you receive."
                >
                  <SettingsListItem
                    title="Project updates"
                    description="Notify me when a project changes."
                    control={(props) => (
                      <Switch
                        {...props}
                        checked={notifications}
                        onCheckedChange={setNotifications}
                      />
                    )}
                  />
                </SettingsList>
              ) : (
                <Card>
                  <CardHeader>
                    <CardTitle>{pages[active].title}</CardTitle>
                    <CardDescription>
                      {pages[active].description}
                    </CardDescription>
                  </CardHeader>
                  <CardContent>
                    <ul className="grid gap-3 text-sm">
                      {pages[active].items.map((item) => (
                        <li key={item}>{item}</li>
                      ))}
                    </ul>
                  </CardContent>
                </Card>
              )}
            </DashboardShellMain>
          </DashboardShellPrimary>
        </DashboardShellInset>
      </DashboardShell>
    </div>
  );
}

Installation

Complete one-time setup, then install:

pnpm dlx shadcn@4.18.0 add @siteplane/application-shell

Files

The command installs src/components/application-shell.tsx and its component dependencies. Render its default export in your application. The registry item and the Code panel contain the same source.

Try the workflow

Choose Overview, Projects, or Settings to change the page in the header and the content area; Settings contains a notification switch. The header button hides and shows the sidebar on desktop and opens it as a sheet at narrow widths.

Composition and adaptation

DashboardShell owns the application layout and SidebarProvider; Sidebar parts render the navigation, and Card and SettingsList provide the page content.

AreaDetails
Dataactive selects the page; pages holds its content; notifications holds the switch value.
CallbacksNavigation clicks update active; Switch onCheckedChange updates notifications.
Included statesActive navigation (aria-current), expanded and hidden desktop sidebar, mobile sheet and a settings page.
Connect to your productReplace active with your router and the page content with your routes. For a viewport-sized app, remove the bounded wrapper and the h-full and min-h-0 classes on DashboardShell and its sidebar; the top bar then stays in view while the content scrolls, and stickyHeader={false} lets the whole page scroll instead. Read the cookie and keyboard shortcut notes on Sidebar. No authentication or backend data is included.