Skip to content

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-008 is triple-booked (#41 shadcn · #72 official Code-Connect · #78 in-house). Exactly one can land on main.
  • 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

  1. 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.
  2. Resolve ARC-ADR-008. Whichever path wins, land exactly one ADR-008 on main; renumber/close the other two. The hub owns the canonical registry — confirm 008 is free.
  3. 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_libraries lists 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.