block · 91/108
Dashboard Shell
Global navigation, page hierarchy, actions, key metrics, and a layout that stacks on narrow screens.
Install
Install manuallyView source on GitHub
Composes
- dashboard-shell.tsx15
- app-sidebar.tsx12
- breadcrumb-trail.tsx1
- metric-card.tsx5
Accessibility4 checks
- The sidebar is an App Sidebar: a labelled nav with aria-current on the active link, a collapse toggle with aria-expanded, and a mobile disclosure that Escape closes.
- The main region is a section named by the page title. The breadcrumb is a labelled nav whose current page uses aria-current="page".
- Each metric is a group named by its label, and trend arrows carry spoken direction text so color is never the only signal.
- Panels are sections named by their h3 titles. Routing, permissions, and data loading remain consumer-owned.
Application surfaces
Northwind Goods
Store overview
Sales and fulfilment across every channel, updated 10 minutes ago.
RevenueIncreased 12.4%
$48,210
vs. $42,890 in August
OrdersIncreased 3.1%
1,284
vs. 1,245 in August
Refund rateDecreased 0.4 pts
1.8%
Lower is better
Time to shipUnchanged No change
1.6 days
Median, all warehouses
Orders needing attention
- #48212h ago
- #48175h ago
- #48091d ago
Revenue by channel
- Online store64%
- Wholesale22%
- Market stalls14%
Code example
usage.tsx"use client";import { BarChart3, LayoutDashboard, Package, Settings, Users } from "lucide-react";import { useState } from "react";import { DashboardShell, DashboardShellAction, DashboardShellActions, DashboardShellBreadcrumb, DashboardShellContent, DashboardShellDescription, DashboardShellHeader, DashboardShellHeading, DashboardShellMain, DashboardShellMetric, DashboardShellMetrics, DashboardShellPanel, DashboardShellPanelTitle, DashboardShellSidebar, DashboardShellTitle, type DashboardShellVariant,} from "@/components/uai/dashboard-shell";import { AppSidebarCollapseToggle, AppSidebarGroup, AppSidebarGroupLabel, AppSidebarHeader, AppSidebarItem, AppSidebarItemBadge, AppSidebarItemIcon, AppSidebarItemLabel, AppSidebarList, AppSidebarMobileTrigger, AppSidebarNav, AppSidebarTitle,} from "@/components/ui/uai/app-sidebar";import { BreadcrumbTrailItem } from "@/components/ui/uai/breadcrumb-trail";import { MetricCardComparison, MetricCardHeader, MetricCardLabel, MetricCardTrend, MetricCardValue,} from "@/components/ui/uai/metric-card";const pages = [ { value: "overview", label: "Overview", icon: LayoutDashboard }, { value: "orders", label: "Orders", icon: Package, badge: "12" }, { value: "customers", label: "Customers", icon: Users }, { value: "reports", label: "Reports", icon: BarChart3 },];const metrics = [ { label: "Revenue", value: "$48,210", trend: "12.4%", direction: "up", note: "vs. $42,890 in August", }, { label: "Orders", value: "1,284", trend: "3.1%", direction: "up", note: "vs. 1,245 in August" }, { label: "Refund rate", value: "1.8%", trend: "0.4 pts", direction: "down", note: "Lower is better", }, { label: "Time to ship", value: "1.6 days", trend: "No change", direction: "flat", note: "Median, all warehouses", },] as const;const attention = [ { id: "#4821", detail: "Address failed validation", age: "2h", tone: "danger" }, { id: "#4817", detail: "Payment under review", age: "5h", tone: "warning" }, { id: "#4809", detail: "Backordered: Linen apron, sand", age: "1d", tone: "warning" },] as const;const channels = [ { name: "Online store", share: 64 }, { name: "Wholesale", share: 22 }, { name: "Market stalls", share: 14 },];export function DashboardShellPreview({ variant = "split" }: { variant?: DashboardShellVariant }) { const [page, setPage] = useState("overview"); return ( <DashboardShell variant={variant} style={{ minHeight: 560 }}> <DashboardShellSidebar value={page} onValueChange={setPage}> <AppSidebarHeader> <AppSidebarTitle>Northwind Goods</AppSidebarTitle> <AppSidebarCollapseToggle /> <AppSidebarMobileTrigger /> </AppSidebarHeader> <AppSidebarNav> <AppSidebarGroup> <AppSidebarGroupLabel>Store</AppSidebarGroupLabel> <AppSidebarList> {pages.map((item) => ( <AppSidebarItem key={item.value} value={item.value} label={item.label} onClick={(event) => { event.preventDefault(); setPage(item.value); }} > <AppSidebarItemIcon> <item.icon size={16} /> </AppSidebarItemIcon> <AppSidebarItemLabel>{item.label}</AppSidebarItemLabel> {item.badge ? <AppSidebarItemBadge>{item.badge}</AppSidebarItemBadge> : null} </AppSidebarItem> ))} </AppSidebarList> </AppSidebarGroup> <AppSidebarGroup> <AppSidebarGroupLabel>Workspace</AppSidebarGroupLabel> <AppSidebarList> <AppSidebarItem value="settings" label="Settings" onClick={(event) => { event.preventDefault(); setPage("settings"); }} > <AppSidebarItemIcon> <Settings size={16} /> </AppSidebarItemIcon> <AppSidebarItemLabel>Settings</AppSidebarItemLabel> </AppSidebarItem> </AppSidebarList> </AppSidebarGroup> </AppSidebarNav> </DashboardShellSidebar> <DashboardShellMain> <DashboardShellHeader> <DashboardShellHeading> <DashboardShellBreadcrumb> <BreadcrumbTrailItem href="#store">Northwind Goods</BreadcrumbTrailItem> <BreadcrumbTrailItem href="#analytics" parent> Analytics </BreadcrumbTrailItem> <BreadcrumbTrailItem current>September 2026</BreadcrumbTrailItem> </DashboardShellBreadcrumb> <DashboardShellTitle>Store overview</DashboardShellTitle> <DashboardShellDescription> Sales and fulfilment across every channel, updated 10 minutes ago. </DashboardShellDescription> </DashboardShellHeading> <DashboardShellActions> <DashboardShellAction>Export CSV</DashboardShellAction> <DashboardShellAction emphasis="primary">New report</DashboardShellAction> </DashboardShellActions> </DashboardShellHeader> <DashboardShellMetrics> {metrics.map((metric) => ( <DashboardShellMetric key={metric.label}> <MetricCardHeader> <MetricCardLabel>{metric.label}</MetricCardLabel> <MetricCardTrend direction={metric.direction} sentiment={metric.label === "Refund rate" ? "positive" : undefined} > {metric.trend} </MetricCardTrend> </MetricCardHeader> <MetricCardValue>{metric.value}</MetricCardValue> <MetricCardComparison>{metric.note}</MetricCardComparison> </DashboardShellMetric> ))} </DashboardShellMetrics> <DashboardShellContent> <DashboardShellPanel> <DashboardShellPanelTitle>Orders needing attention</DashboardShellPanelTitle> <ul style={{ display: "grid", gap: 2, margin: 0, padding: 0, listStyle: "none" }}> {attention.map((order) => ( <li key={order.id} style={{ display: "flex", gap: 10, alignItems: "center", minWidth: 0, padding: "5px 0", }} > <span aria-hidden="true" style={{ width: 6, height: 6, flex: "none", borderRadius: 999, background: `var(--uai-${order.tone})`, }} /> <span style={{ fontWeight: 500, fontVariantNumeric: "tabular-nums" }}> {order.id} </span> <span style={{ flex: 1, minWidth: 0, overflow: "hidden", color: "var(--uai-muted)", textOverflow: "ellipsis", whiteSpace: "nowrap", }} > {order.detail} </span> <span style={{ flex: "none", color: "var(--uai-subtle)", fontSize: 12, fontVariantNumeric: "tabular-nums", }} > {order.age} ago </span> </li> ))} </ul> </DashboardShellPanel> <DashboardShellPanel> <DashboardShellPanelTitle>Revenue by channel</DashboardShellPanelTitle> <ul style={{ display: "grid", gap: 12, margin: 0, padding: 0, listStyle: "none" }}> {channels.map((channel) => ( <li key={channel.name} style={{ display: "grid", gap: 6 }}> <span style={{ display: "flex", justifyContent: "space-between" }}> <span style={{ color: "var(--uai-muted)" }}>{channel.name}</span> <span style={{ fontVariantNumeric: "tabular-nums", fontWeight: 500 }}> {channel.share}% </span> </span> <span aria-hidden="true" style={{ height: 4, overflow: "hidden", borderRadius: 999, background: "var(--uai-surface-raised)", }} > <span style={{ display: "block", width: `${channel.share}%`, height: "100%", borderRadius: 999, background: "var(--uai-text)", opacity: 0.7, }} /> </span> </li> ))} </ul> </DashboardShellPanel> </DashboardShellContent> </DashboardShellMain> </DashboardShell> );}Manual installation
dashboard-shell.tsxLoading component source…