Blocks
Workspace dashboard
Full app shell — 232px sidebar (sections, badges, credits widget, user footer) + sticky topbar (breadcrumbs + search + actions) + page header + 4 KPI cards + tabs + table with Owner column.
Preview
Live preview
Renders the block exactly as it appears on the home page.
// Workspace dashboard — exact source of the block shown on the home page.
// Visual classes (.artboard, .dash-*, .kpi, .kpi-spark) live in globals.css.
import * as React from "react";
import { Avatar, Badge, Breadcrumbs, Button, Table, type TableColumn } from "@/components/br/primitives";
import {
Arrow, Bell, Bolt, Calendar, Chevron, Download, Edit, Filter, Folder, Grid,
Home, Inbox, Logo, Mail, Plus, Search, Settings, Sort, Spark, Trend, Users,
} from "@/components/br/icons";
type Row = {
name: string;
email: string;
plan: "Studio" | "Pro" | "Starter";
status: "active" | "paused" | "trial";
mrr: number;
joined: string;
owner: string;
};
const DASH_ROWS: Row[] = [
{ name: "Helio Energy", email: "ops@helio.io", plan: "Studio", status: "active", mrr: 12400, joined: "Mar 04", owner: "BR" },
{ name: "Verba Editorial", email: "team@verba.press", plan: "Pro", status: "active", mrr: 4800, joined: "Feb 11", owner: "JK" },
{ name: "Mara Booking", email: "finance@mara.co", plan: "Pro", status: "paused", mrr: 4800, joined: "Jan 22", owner: "BR" },
{ name: "Atlas Robotics", email: "cto@atlas.dev", plan: "Studio", status: "active", mrr: 12400, joined: "Jan 09", owner: "MN" },
{ name: "Pebble Health", email: "hi@pebble.health", plan: "Starter", status: "trial", mrr: 0, joined: "Dec 28", owner: "BR" },
{ name: "Northwind Audio", email: "finance@nw.audio", plan: "Pro", status: "active", mrr: 4800, joined: "Dec 12", owner: "JK" },
];
function SparkSvg({
vals,
w = 56,
h = 18,
color = "currentColor",
}: {
vals: number[];
w?: number;
h?: number;
color?: string;
}) {
const max = Math.max(...vals);
const min = Math.min(...vals);
const pts = vals
.map((v, i) => {
const x = (i / (vals.length - 1)) * w;
const y = h - ((v - min) / (max - min || 1)) * h;
return `${x.toFixed(1)},${y.toFixed(1)}`;
})
.join(" ");
return (
<svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} className="kpi-spark">
<polyline
points={pts}
fill="none"
stroke={color}
strokeWidth="1.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function DashboardExample() {
const [tab, setTab] = React.useState<"clients" | "active" | "paused" | "trial">("clients");
const kpis = [
{ label: "MRR", value: "€48,400", delta: "+12.4%", dir: "up" as const, icon: <Bolt size={14} /> },
{ label: "Clients", value: "142", delta: "+6", dir: "up" as const, icon: <Users size={14} /> },
{ label: "Active now", value: "38", delta: "0", dir: "flat" as const, icon: <Spark size={14} /> },
{ label: "Churn", value: "1.8%", delta: "−0.4%", dir: "down" as const, icon: <Trend size={14} /> },
];
const columns: TableColumn<Row>[] = [
{
key: "user",
label: "Client",
kind: "user",
render: (r) => (
<>
<Avatar
fallback={r.name
.split(" ")
.map((x) => x[0])
.join("")
.slice(0, 2)}
accent={r.status === "active"}
/>
<div className="meta">
<span className="name">{r.name}</span>
<span className="email">{r.email}</span>
</div>
</>
),
},
{
key: "plan",
label: "Plan",
render: (r) => <Badge variant={r.plan === "Studio" ? "accent" : "default"}>{r.plan}</Badge>,
},
{
key: "status",
label: "Status",
render: (r) => (
<Badge variant={r.status === "active" ? "success" : r.status === "paused" ? "default" : "accent"} dot>
{r.status}
</Badge>
),
},
{
key: "owner",
label: "Owner",
render: (r) => <Avatar fallback={r.owner} size="sm" />,
},
{
key: "mrr",
label: "MRR",
numeric: true,
render: (r) => <>€{r.mrr.toLocaleString("en-US")}</>,
},
{ key: "joined", label: "Joined", numeric: true, render: (r) => r.joined },
{
key: "a",
label: "",
kind: "actions",
render: () => (
<button aria-label="edit">
<Edit size={14} />
</button>
),
},
];
return (
<div className="artboard">
<div className="artboard-chrome">
<div className="lights">
<i />
<i />
<i />
</div>
<div className="url">atelier.brych.studio / clients</div>
<div className="badge-meta">EX · 02 · DASHBOARD</div>
</div>
<div className="dash-frame">
{/* Sidebar */}
<aside className="dash-sidebar">
<div className="dash-sidebar-brand">
<Logo size={20} /> Atelier
<span className="ws-meta">BR · Studio</span>
</div>
<div className="dash-sidebar-section">Workspace</div>
<button type="button" className="dash-sb-item">
<Home size={15} /> <span>Overview</span>
</button>
<button type="button" className="dash-sb-item active">
<Users size={15} /> <span>Clients</span> <Badge variant="accent">6</Badge>
</button>
<button type="button" className="dash-sb-item">
<Folder size={15} /> <span>Projects</span> <Badge>12</Badge>
</button>
<button type="button" className="dash-sb-item">
<Inbox size={15} /> <span>Inbox</span>{" "}
<Badge variant="success" dot>
3
</Badge>
</button>
<button type="button" className="dash-sb-item">
<Calendar size={15} /> <span>Calendar</span>
</button>
<div className="dash-sidebar-section">Finance</div>
<button type="button" className="dash-sb-item">
<Trend size={15} /> <span>Revenue</span>
</button>
<button type="button" className="dash-sb-item">
<Mail size={15} /> <span>Invoices</span>
</button>
<div className="dash-sidebar-section">Studio</div>
<button type="button" className="dash-sb-item">
<Edit size={15} /> <span>Journal</span>
</button>
<button type="button" className="dash-sb-item">
<Settings size={15} /> <span>Settings</span>
</button>
{/* Credits widget */}
<div className="dash-credits" role="group" aria-label="Credits remaining">
<div className="dash-credits-head">
<Bolt size={11} />
<span className="lbl">Credits</span>
<span className="plan">STUDIO</span>
</div>
<div className="dash-credits-num">
<span className="used">1,240</span>
<span className="div">/</span>
<span className="total">2,000</span>
<span className="unit">used</span>
</div>
<div className="dash-credits-bar">
<i style={{ width: "62%" }} />
</div>
<div className="dash-credits-foot">
<span className="resets">Resets Jun 01</span>
<a className="up" href="#" onClick={(e) => e.preventDefault()}>
Upgrade <Arrow size={11} />
</a>
</div>
</div>
<div className="dash-sb-foot">
<Avatar size="sm" fallback="BR" accent />
<div className="meta">
<span className="name">Bartosz R.</span>
<span className="role">Owner</span>
</div>
<Button variant="ghost" size="sm" style={{ marginLeft: "auto" }} aria-label="Open account menu">
<Chevron size={12} />
</Button>
</div>
</aside>
{/* Main */}
<div className="dash-main">
<header className="dash-topbar">
<Breadcrumbs items={[{ label: "Studio" }, { label: "Workspace" }, { label: "Clients" }]} />
<div className="dash-topbar-search">
<Search size={13} />
<span>Search clients, invoices…</span>
<span className="kbd">⌘K</span>
</div>
<div className="dash-topbar-actions">
<Button variant="ghost" size="sm" aria-label="View notifications">
<Bell size={14} />
</Button>
<Button variant="ghost" size="sm" aria-label="Open settings">
<Settings size={14} />
</Button>
<Avatar size="sm" fallback="BR" accent />
</div>
</header>
<div className="dash-body">
<div className="dash-header">
<div>
<div className="eyebrow">§ Q2 · 2026</div>
<h1>Clients</h1>
<p className="sub">142 total · 38 active this week · €48.4k MRR</p>
</div>
<div className="dash-header-actions">
<Button variant="ghost" size="sm" icon={<Download size={13} />}>
Export
</Button>
<Button variant="ghost" size="sm" icon={<Filter size={13} />}>
Filter
</Button>
<Button variant="primary" size="sm" icon={<Plus size={13} />}>
New client
</Button>
</div>
</div>
<div className="dash-kpis">
{kpis.map((k, i) => (
<div className="kpi" key={k.label}>
<div className="kpi-label">
{k.icon}
<span>{k.label}</span>
</div>
<div className="kpi-value">{k.value}</div>
<div className={`kpi-delta ${k.dir}`}>
{k.dir === "up" ? "↑" : k.dir === "down" ? "↓" : "→"} {k.delta}
<span style={{ color: "var(--text-subtle)", marginLeft: 4 }}>vs last month</span>
</div>
<SparkSvg
vals={[3, 4, 3.5, 5, 4.5, 6, 5.5, 7, 6.5, 8].map((v) => v + i)}
color={k.dir === "up" ? "#16A34A" : k.dir === "down" ? "#DC2626" : "var(--text-subtle)"}
/>
</div>
))}
</div>
<div className="dash-table-wrap">
<div className="dash-table-head">
<div style={{ display: "flex", alignItems: "baseline" }}>
<span className="ttl">All clients</span>
<span className="meta">{DASH_ROWS.length} of 142</span>
</div>
<div className="dash-table-head-actions">
<Button variant="ghost" size="sm" icon={<Sort size={12} />}>
Sort
</Button>
<Button variant="ghost" size="sm" aria-label="Switch table density">
<Grid size={12} />
</Button>
</div>
</div>
<div className="dash-tabs" role="tablist" aria-label="Client filters">
<button type="button" role="tab" aria-selected={tab === "clients"} onClick={() => setTab("clients")}>
All <Badge>142</Badge>
</button>
<button type="button" role="tab" aria-selected={tab === "active"} onClick={() => setTab("active")}>
Active{" "}
<Badge variant="success" dot>
38
</Badge>
</button>
<button type="button" role="tab" aria-selected={tab === "paused"} onClick={() => setTab("paused")}>
Paused <Badge>9</Badge>
</button>
<button type="button" role="tab" aria-selected={tab === "trial"} onClick={() => setTab("trial")}>
Trial <Badge variant="accent">4</Badge>
</button>
</div>
<Table columns={columns} rows={DASH_ROWS} />
</div>
</div>
</div>
</div>
</div>
);
}Prompt an AI agent
Paste this into your AI coding agent (Claude Code, Cursor, …) to have it build this exact screen in your project. It assumes the BR-UI registry — see For AI agents for the one-time setup prompt.
Build a "Workspace dashboard" screen using the BR-UI design system.
SETUP (once):
npx shadcn@latest add https://ds.bartoszrychlicki.com/r/br-ui-all.json
Needed items: br-tokens, br-component-styles, br-primitives, br-icons.
br-component-styles ships the .artboard / .dash-* / .kpi CSS classes used below.
RULES:
- All colors via CSS vars (--accent, --text, --text-muted/-subtle, --surface-1..3,
--border). Never hex.
- Eyebrows use font-meta uppercase; the page title uses font-display.
- One accent moment per block. Variants switch via data-variant=A|B|C on <html>.
- Import primitives from @/components/br/primitives and icons from @/components/br/icons.
LAYOUT — a .dash-frame grid: 232px .dash-sidebar + 1fr .dash-main.
SIDEBAR (.dash-sidebar):
- Brand: <Logo size={20}/> "Atelier" + a .ws-meta "BR · Studio".
- Section "Workspace": Overview, Clients (active item, <Badge variant="accent">6</Badge>),
Projects (<Badge>12</Badge>), Inbox (<Badge variant="success" dot>3</Badge>), Calendar.
- Section "Finance": Revenue, Invoices. Section "Studio": Journal, Settings.
Each row is a .dash-sb-item button: lucide icon (size 15) + label.
- Credits widget (.dash-credits): head "Credits / STUDIO", "1,240 / 2,000 used",
62% progress bar, foot "Resets Jun 01" + "Upgrade →".
- Footer (.dash-sb-foot): <Avatar size="sm" fallback="BR" accent/> + "Bartosz R. / Owner"
+ a ghost chevron button.
MAIN (.dash-main):
- Topbar (.dash-topbar): <Breadcrumbs items={[Studio, Workspace, Clients]}/>, a search
pill "Search clients, invoices…" with a ⌘K kbd, ghost Bell + Settings buttons, avatar.
- Header (.dash-header): eyebrow "§ Q2 · 2026", <h1>Clients</h1>, sub
"142 total · 38 active this week · €48.4k MRR"; right side: ghost "Export" (Download),
ghost "Filter", primary "New client" (Plus).
- KPIs (.dash-kpis, 4 .kpi cards): MRR €48,400 ↑ +12.4%; Clients 142 ↑ +6;
Active now 38 → 0; Churn 1.8% ↓ −0.4%. Each: icon + label, big value, a delta
(green up / red down / muted flat) with "vs last month", and a small inline SVG
sparkline (10-point polyline).
- Table block (.dash-table-wrap): head "All clients / N of 142" + ghost Sort + Grid;
tabs (role="tablist"): All 142 / Active (success 38) / Paused 9 / Trial (accent 4);
then <Table> with columns — Client (avatar + name/email, accent avatar when active),
Plan (<Badge variant="accent"> for Studio else default), Status (success/default/accent
dot badge), Owner (sm avatar), MRR (numeric €), Joined (numeric), edit action.
Rows: Helio Energy/Studio/active/€12,400, Verba Editorial/Pro/active/€4,800,
Mara Booking/Pro/paused/€4,800, Atlas Robotics/Studio/active/€12,400,
Pebble Health/Starter/trial/€0, Northwind Audio/Pro/active/€4,800.
The verbatim reference source is in the code block above — match it 1:1.