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.