A workspace that fits your product. Three navigation layouts, a responsive grid, and composable cards with gentle motion. Try every arrangement in the expanded playground.
Your workspace, your way.
Change the shell. Keep everything inside.
pnpm dlx shadcn@latest add @latheui/dashboardPart of Lathe Pro. Set up your project once with your license key: npx latheui init --key <key>. See Lathe Pro.
Dashboard is part of Lathe Pro
The preview is live and free to try. The source, the install and every assembly come with Pro: $79 once, with lifetime updates.
Depends on
Accepts native section attributes, children, className and style. Give the workspace an accessible name. Navigation slots are ordinary React nodes; your application owns routing and selection.
| Prop | Type | Default | Description |
|---|---|---|---|
| layout | "combined" | "topbar" | "sidebar" | "combined" | Both navigation regions, top navigation only, or sidebar only. |
| topbar / sidebar | ReactNode | — | Replaceable navigation slots. The sidebar is visually hidden below 48rem of shell width. |
| mobileNavigation | ReactNode | — | Compact navigation shown below 48rem when the layout includes a sidebar. Supply this to keep sidebar-only navigation reachable on mobile. |
| collapsed | boolean | false | Controlled sidebar width. Render icon-only sidebar content when collapsed and provide accessible names. |
| sidebarWidth / collapsedWidth | number | 208 / 64 | Expanded and collapsed sidebar widths in pixels. |
| contentClassName | string | — | Override content padding or other content styles. |
Accepts native div attributes. Put it inside Dashboard to use its content container queries.
| Prop | Type | Default | Description |
|---|---|---|---|
| columns | 2 | 3 | 4 | 6 | 12 | 4 | Desktop columns. Below 32rem of content width, cards stack in one column. |
| gap | number | 16 | Space between cards in pixels. |
| rowHeight | number | — | Optional minimum height per grid row in pixels; rows grow with content. |
Accepts Motion section props, className, style and any React children. Stable keys preserve card state during grid changes.
| Prop | Type | Default | Description |
|---|---|---|---|
| title / description / action | ReactNode | — | Optional heading, supporting text and header action. |
| span | number | "full" | 1 | Column span, clamped to available columns. Full spans the entire row. |
| rowSpan | number | 1 | Number of rows occupied by the card. |
| variant | "bordered" | "soft" | "plain" | "bordered" | Card surface treatment; override with className for accent cards. |
| contentClassName | string | — | Styles the card body without changing its heading. |
In the combined layout, the top navigation chooses a main page and the sidebar chooses a section within that page. Keep a section selection per page to remember where people left off. The demo moves section navigation below the header in top-only mode and provides compact navigation on mobile. Navigation stays in the slots, so you can use your own router and links.
const sections = {
overview: ["Summary", "Activity", "Budget"],
manage: ["Cards", "Controls", "Limits"],
rewards: ["Membership", "Benefits", "History"],
}
type Page = keyof typeof sections
const [page, setPage] = useState<Page>("overview")
const [selected, setSelected] = useState({
overview: "Summary", manage: "Cards", rewards: "Membership",
})
const sectionNavigation = (
<nav aria-label={page + " sections"}>
{sections[page].map(section => (
<button key={section} type="button"
aria-current={selected[page] === section ? "page" : undefined}
onClick={() => setSelected(current => ({ ...current, [page]: section }))}>
{section}
</button>
))}
</nav>
)
<Dashboard layout="combined"
topbar={<YourPageNavigation value={page} onValueChange={setPage} />}
sidebar={sectionNavigation} mobileNavigation={sectionNavigation}>
<YourPage page={page} section={selected[page]} />
</Dashboard>Keep layout and collapse state wherever your app needs it. Contents stay mounted while the shell arrangement changes.
const [layout, setLayout] = useState<DashboardLayout>("combined")
const [collapsed, setCollapsed] = useState(false)
<Dashboard layout={layout} collapsed={collapsed}
topbar={<YourTopNavigation onLayoutChange={setLayout} />}
sidebar={<YourSidebar collapsed={collapsed} onToggle={() => setCollapsed(!collapsed)} />}
mobileNavigation={<YourMobileNavigation />}>
{children}
</Dashboard>Use spans for a dense desktop grid. Spans clamp automatically when the workspace stacks on smaller screens. Pass existing Lathe components or any third-party React component as children; each keeps its own API.
<DashboardGrid columns={12} gap={20} rowHeight={120}>
<DashboardCard title="Revenue" span={4}><YourMetric /></DashboardCard>
<DashboardCard title="Trends" span={8} rowSpan={2}><YourChart /></DashboardCard>
<DashboardCard title="Tasks" span={4}><YourTaskList /></DashboardCard>
<DashboardCard title="Transactions" span="full">
<div className="overflow-x-auto"><YourTable /></div>
</DashboardCard>
</DashboardGrid>The primitives inherit Lathe’s semantic color tokens, including dark mode. The orange financial workspace is just an example composition, not built into the shell. Override surfaces, spacing and radii with className or style.
<Dashboard className="rounded-3xl bg-muted/30" contentClassName="p-6">
<DashboardGrid columns={3} gap={24}>
<DashboardCard title="A custom surface" variant="plain"
className="rounded-3xl bg-orange-600 text-white" span={2}>
Any React content
</DashboardCard>
</DashboardGrid>
</Dashboard>