Uptoolkit
GitHub

Dashboard Shell

Sidebar and header app shell for a dashboard, with grouped nav, a workspace switcher, and a content slot.

Dashboard

Page content renders in the shell's content slot.

Installation

npx shadcn@latest add https://ui.uptoolkit.com/r/dashboard-shell.json

Usage

A grouped sidebar with a workspace switcher on the left, a header with search, create, messages, notifications, a language switcher, and an account menu on top, wrapped around a children content slot. The sidebar collapses into a Sheet under lg and can be toggled away above it.

import { DashboardShell } from "@/components/dashboard-shell";

<DashboardShell
  workspace={{ id: "my-space", name: "My Space", meta: "Personal" }}
  workspaces={workspaces}
  user={viewer}
  nav={[
    {
      label: "Explore",
      items: [
        { label: "Dashboard", href: "/dashboard", icon: IconLayoutGrid, active: true },
        { label: "Marketplace", href: "/marketplace", icon: IconBuildingStore },
      ],
    },
  ]}
  messagesCount={1}
  notificationsCount={4}
  onSearch={(query) => router.navigate({ to: "/search", search: { q: query } })}
  onSignOut={() => signOut()}
>
  {children}
</DashboardShell>;

Routing

Nav items and group headings are plain data: each item is an <a> with an active flag, not a router Link. Swap the anchor inside group.items.map for your router's link component and drive active from the current route.

Sidebar state

The sidebar is uncontrolled by default — pass defaultSidebarOpen={false} to start collapsed, or pass sidebarOpen and onSidebarOpenChange to persist it in a cookie or user preference so the choice survives a reload. Under lg the toggle is replaced by a Sheet, which closes itself when a nav item is picked.

Header actions

Every header affordance is opt-in: the search field, create button, messages button, and language switcher only render when their prop is passed (onSearch, onCreateClick, onMessagesClick, locales), so routes that don't need one can drop the prop instead of hiding an empty control. searchShortcut is display-only — bind the actual hotkey wherever your command palette lives.

The floating support button in the bottom corner renders only when onSupportClick is passed.