ADR-0018: Design System Architecture — Tokens, Themes, and Tooling
- Status: accepted
- Deciders: Reasonix agent (2026-06-27)
- Date: 2026-06-27
- Issue: #279
Context
Neemias uses Tailwind CSS 4.1 configured via CSS-first @theme, shadcn/ui (42 components, 29 backed by Radix), and has MUI 7.3.5 installed but completely unused (zero imports across the entire codebase). A design system survey revealed five problems:
- MUI + Emotion are dead weight — 4 packages, ~1.5MB, zero imports. No
ThemeProvider, nocreateTheme, no component usage. - next-themes is installed but non-functional —
useTheme()is called insonner.tsx, but noThemeProviderwraps the application. The.darkclass on<html>(which Tailwind v4 uses) is never toggled by JS. - fonts.css is an empty placeholder — imported by
index.cssbut contains zero@font-facedeclarations. - Two-tier color strategy — shadcn/ui primitives use semantic tokens (
bg-primary,text-muted-foreground), but business components use raw Tailwind palette colors (bg-red-50,text-green-700). - Token gaps in dark mode —
--input-background,--switch-background,--font-size,--font-weight-*have no dark mode values.
Decision
1. Component library: shadcn/ui (keep), MUI (remove)
Keep shadcn/ui as the sole component library. It's already integrated, backed by Radix primitives, and uses Tailwind tokens natively.
Remove @mui/material, @mui/icons-material, @emotion/react, and @emotion/styled from dependencies. They have zero imports and represent ~1.5MB of dead bundle weight.
Rationale: Adding MUI alongside shadcn/ui would create a dual design system with conflicting styling approaches (CSS-in-JS vs utility classes), inconsistent accessibility APIs, and double the bundle size. MUI was never adopted; removing it eliminates dead code and simplifies the dependency tree.
2. Theme switching: our own useTheme() hook (superseded by ADR-0019, q.v.)
Superseded. ADR-0019 replaces next-themes with a custom themes.ts hook using data-theme attribute, supporting 4 built-in themes (light, warm, dark, ijcp) and user custom themes post-MVP.
3. Typography: self-hosted variable fonts
Use Inter (sans-serif, weight 100–900) and JetBrains Mono (monospace, weight 400–700) as self-hosted WOFF2 variable fonts via @font-face in fonts.css.
Rationale: Self-hosting avoids Google Fonts' GDPR/LGPD consent requirements and eliminates a render-blocking external request. Variable fonts provide the full weight range in a single file (~300KB for Inter, ~150KB for JetBrains Mono). Both are open-source (SIL OFL).
4. Design token format: CSS custom properties (primary), JSON export (secondary)
Keep CSS custom properties in theme.css as the source of truth. Generate a JSON export for external tooling (Figma, documentation). Do not create a TypeScript theme object — Tailwind v4's @theme inline bridge is the single mapping layer.
Rationale: The CSS is already the source of truth (181 lines, 39 tokens). A TypeScript theme object would be a duplicate that drifts. If tooling needs tokens, generate JSON from the CSS.
5. Color strategy: semantic tokens everywhere
Require all components (business and UI) to use semantic design tokens (bg-primary, text-muted-foreground). Audit and replace raw Tailwind palette classes (bg-red-50, text-green-700) with semantic equivalents.
Rationale: The current split creates a situation where changing a color in theme.css has no effect on business components. Semantic tokens are the design system's contract — using them consistently ensures theme changes propagate everywhere.
Consequences
Positive
- Removes ~1.5MB of dead dependencies
- Activates dark mode toggle (already defined in CSS, never wired up)
- Single source of truth for colors in
theme.css - Self-hosted fonts eliminate GDPR consent requirement
- Bundle size reduction measurable in CI
Negative
- Breaking change: removes MUI from dependency tree (mitigation: zero imports, no runtime impact)
- Font files add ~450KB to the repository (mitigation: WOFF2, variable fonts, single request)
- Color audit is manual work across ~15 component files
Neutral
- next-themes stays (already a dependency)
- shadcn/ui is unchanged
- Tailwind v4 configuration is unchanged