Decision Brief: shadcn/ui (#41) vs in-house untool DS (#78)¶
Status: OPEN — awaiting direction call. This is the #1 blocker for the whole design-system track (see
STATUS.md§3). Everything else is downstream of it.Audience: the person making the call. Framed for a decision, not a re-investigation. Compiled: 2026-05-29 from PR #41, PR #78, the live Figma file, and a local build verification.
The question in one sentence¶
Do we adopt shadcn/ui (vendored industry-standard primitives, PR #41) or the in-house
"untool.ai" design system (bespoke brand + data-viz primitives, PR #78) as the foundation
frontend-core builds on — and which single ARC-ADR-008 becomes canonical?
Why it's stuck¶
- The spike (#41) recommended shadcn/ui; the consolidation (#78) overrode that with an in-house system — but the override was made inside a draft PR and never ratified.
ARC-ADR-008is triple-booked (#41 shadcn · #72 official Code-Connect · #78 in-house). Exactly one can land onmain.- Both PRs are open drafts; nothing design-system-differentiated has merged.
The key insight: these are not pure substitutes¶
| shadcn/ui (#41) | in-house untool DS (#78) | |
|---|---|---|
| What it gives you | Generic interactive chrome: button, badge, card, dialog, input, table | Brand identity + data-viz/density primitives: Sparkline, SmallMultiples, Numeric, Annotation, Surface, Rule, Wordmark |
| Where it wins | Forms, dialogs, tables — the "boring but essential" accessible plumbing | The differentiated analytics/cockpit surface this product actually is |
| What it lacks | No brand, no data-viz, no Figma bridge | No upstream ecosystem; the generic primitives (Button/Field/Badge) are hand-rolled and thinner than Radix |
shadcn covers the commodity 80%; the untool DS covers the differentiated 20% that shadcn will never ship (sparklines tuned to your cockpit, your wordmark, your voice). That asymmetry is the most important fact in this decision — it's why a hybrid is on the table (Option C).
Option A — shadcn/ui (PR #41)¶
Delivered: components/ui/{button,badge,card,dialog,input,table}.tsx + lib/utils.ts;
app/design-lab/{mui,shadcn} head-to-head benchmark routes; e2e/design-lab-a11y.spec.ts
(6/6 axe green); scripts/measure-design-lab.mjs; ARC-ADR-008-frontend-design-system.md.
Measured (gzipped marginal over CopilotKit baseline): shadcn +9.4 KB JS / ~14 KB all-in vs MUI ~75 KB. RSC-native, zero runtime, components vendored in-repo.
| ✅ Strengths | ⚠️ Costs / risks |
|---|---|
| ~5× lighter than MUI; passes the bundle gate easily | Stock defaults failed 3 contrast checks (tuned to pass — must keep tuned tokens) |
| Radix = accessible primitives for free (focus, ARIA, dialog semantics) | Tailwind v4 cascade-layer clash with globals.css a{} — needs @layer base fix before broad rollout |
| Huge ecosystem; low in-house maintenance | Brings in Tailwind v4 — a second styling paradigm next to the repo's CSS Modules |
CSS-var theming continuous with existing theme.css → incremental token migration |
No brand identity, no data-viz primitives, no Figma bridge |
Spike is "not for merge as-is"; design-lab routes are scaffolding to retire |
Option B — in-house untool DS (PR #78)¶
Delivered: 12 primitives (Wordmark, Text, Stack, Surface, Rule, Button, Field, Badge,
Numeric, Sparkline, SmallMultiples, Annotation) each with .module.css + .stories.tsx;
9 Storybook foundation pages; untool.css (--ut-* token layer, AA-audited light+dark);
brand docs (UNTOOL.md, VOICE.md); lib/figma/{registry,storybook,types}.ts (35-entry
registry, 12 wired to real, verified Figma node IDs); /design-system gallery page;
homegrown Code Connect Lite plugin + figma:{validate,emit,verify} scripts;
ARC-ADR-008-untool-design-system-and-code-connect-lite.md. 75 files, +8,982/−910.
| ✅ Strengths | ⚠️ Costs / risks |
|---|---|
| Bespoke brand identity + voice — true product differentiation | All maintenance is in-house; no upstream a11y/security updates |
| Data-viz primitives tuned to the analytics product (shadcn has none) | Generic primitives (Button/Field/Badge) are hand-rolled, thinner than Radix |
| Real Figma bridge, verified live (avoids paid Code Connect) | Homegrown Figma sync is another system to maintain |
| Stays on CSS Modules — one styling paradigm | Large review surface (75 files); Badge 4-vs-5 tone drift (Figma vs code) open |
Figma side is a v0 skeleton — designers must re-author; 23 registry entries still PENDING |
Option C — Hybrid (not yet written up; the honest third path)¶
shadcn/ui for commodity interactive chrome (button, dialog, input, table — vendored,
accessible, cheap) + the untool brand token layer + the bespoke data-viz primitives on top.
Captures shadcn's accessibility/maintenance win on the boring 80% and the untool
differentiation on the 20% that matters. The friction: it means running Tailwind (shadcn) and
CSS Modules (untool primitives) side by side — manageable, but a deliberate choice, and the
untool.css ↔ shadcn CSS-var token layers must be unified to one source of truth.
Recommendation¶
- Decide direction. My read: Option C (hybrid) is the strongest — neither pure option is complete (A has no brand/data-viz; B re-implements commodity primitives the ecosystem gives for free). If a hybrid is too much surface to govern right now, A then B (ship accessible chrome fast, layer brand on top) beats B-alone on time-to-value and a11y risk.
- Resolve
ARC-ADR-008. Whichever path wins, land exactly one ADR-008 onmain; renumber/close the other two. The hub owns the canonical registry — confirm008is free. - Then the open PRs collapse cleanly: A → close #78/#65/#66/#72; B → close #41/#65/#66/#72; C → cherry-pick from both. #70 (token-drift cleanup) lands independently either way.
This brief intentionally stops at framing + a recommendation — the call is yours.
Appendix — Figma finding (resolves STATUS.md §5 open item)¶
get_libraries on the untool DS file (iBY4OonbLshdzBnqOg2WUC) shows it currently subscribes
to external community kits: Material 3 Design Kit, Figma's Simple Design System, and the
Apple iOS/iPadOS/macOS/watchOS/visionOS kits. Two implications worth noting:
- Even the "in-house" Figma file is scaffolded on external UI kits today — notably Figma's Simple Design System (Figma's own code-backed React kit, shadcn-adjacent). The "pure in-house" framing is softer than it looks; the Apple kits also tie into the #62 iOS-mirror epic.
- Publish status still unconfirmed:
get_librarieslists libraries added to this file, not whether this file is itself published as a consumable team library. That must be checked manually (Figma Assets panel → publish state) — until it's published, downstream files (and the iOS mirror) can't consume it as a library.