Appearance
0027 — Web Frontend & Design-System Tooling (Claude Design) ​
Status: Accepted (2026-06-21)
Amended by: ADR 0031 — Code-First Design Workflow & Cross-Platform Delivery (2026-06-21) — establishes packages/ui/src/* as the single source of truth and the code-first design loop that supersedes the HTML-prototype direction described below.
Date: 2026-06-21
ADO work item: AB#3237 (Story — Design system / @hch/ui built and adopted)
Deciders: Kristopher Turner (platform owner)
Context ​
ADR 0008 names packages/ui as the shared package layer housing "the design system + Storybook" and requires every surface to consume it. The platform strategy is equally explicit: every web and mobile screen must import presentational components and design tokens from the @hch/ui package; no surface holds its own colours, spacing, or component logic.
Despite this, the repo arrived at the start of Platform build (AB#3074) with:
- No ADR for the design tool.
docs/internal/design/claude-design.mddocuments the workflow well, but the repo's own "decide before building" gate requires an Accepted ADR before code depends on any significant tool choice. None existed for the UI toolchain. - ADO items closed without delivery. Feature AB#3160 → Story AB#3237 → Tasks AB#3238/3239 were marked Closed. However the
DesignSync/Handoff loop was never run, and the web app (apps/web) contained 25+ hand-coded, inline-styled pages importing nothing from@hch/ui. Closed ≠ done (ADO acceptance criterion: "UI built via design system"). - A token discrepancy. The canonical spec in
claude-design.md §4gives the app background as#F5F6F8(Quiet Mist Gray);packages/ui/src/tokens.tsand its README had#F5F5F0. One value must win.
This ADR locks the tool choice and the source-of-truth chain so that every future screen has a single, unambiguous place to look for the visual language.
Decision ​
Claude Design (
claude.ai/design, Anthropic Labs) is the sole sanctioned UI design tool for Heritage Virginia Community Hub. Google Stitch is retired. Everyapps/webandapps/mobilescreen imports its presentational components and design tokens exclusively from@hch/ui(packages/ui/src/). No inline-styled, hand-coded pages remain in the shipped application.
Project reference ​
Claude Design project: "Heritage Virginia Community Hub" ID: ceb2fb20-8346-4a74-8683-a96e4663b525 Type: DESIGN_SYSTEM
Source-of-truth chain ​
The flow is one direction top-down; each arrow means "is upstream of":
docs/internal/design/claude-design.md (§4 — the canonical visual spec)
│ sync-UP source cards authored to match §4
▼
packages/ui/design-system/*.html ──/design-sync──► Claude Design project
(HTML @dsCard preview cards) "Heritage Virginia Community Hub"
▲ │
│ (later) regenerate cards from real components │ design screens / prototypes
│ ▼
packages/ui/src/* ◄──────────── "Handoff to Claude Code" ───── finished prototype
(runtime React components + tokens — what the app actually ships)
│
▼
apps/web/** · apps/mobile/** ← import ONLY from @hch/ui; no inline stylesclaude-design.md §4is the canonical spec. Any colour, spacing, or component rule change starts here.packages/ui/design-system/is the only folder tied to Claude Design. It holds HTML preview cards (<!-- @dsCard group="…" -->) pushed up to the project via/design-sync. Never a wholesale replace — push incrementally, one card at a time. After uploading, callregister_assets; the auto-index does not reliably build the manifest. Then reload the Claude Design project page in the browser.packages/ui/src/is where the Handoff deposits real React/React Native code. This is the runtime library that ships as@hch/ui.apps/webandapps/mobileimport exclusively from@hch/ui. No surface holds its own palette constants, spacing magic numbers, or primitive components.
The @hch/ui import rule (hard constraint) ​
Every page and component in apps/web and apps/mobile must:
ts
import { Button, Card, Badge, colors, fonts } from '@hch/ui';Inline style={{ color: '#2C5AA0' }} (hard-coded hex) or style={{ background: '#F5F6F8' }} where a token or component from @hch/ui covers the use-case is a violation. The acceptance criterion for any story that touches a page: no page ships with hand-coded style props for values already in @hch/ui.
The current violation at the time of this ADR: 25+ pages in apps/web/src/pages/ were written before this rule was enforced. Closing this gap is tracked under Story AB#3237 and its tasks.
Token resolution ​
The token conflict between #F5F6F8 (spec) and #F5F5F0 (tokens.ts / README) is resolved in favour of the spec. #F5F6F8 is the single value for colors.background (Quiet Mist Gray) everywhere:
| Location | Correct value |
|---|---|
claude-design.md §4 | #F5F6F8 (already correct — this is the source) |
packages/ui/src/tokens.ts | #F5F6F8 (corrected by this ADR) |
packages/ui/README.md | #F5F6F8 (corrected by this ADR) |
packages/ui/design-system/foundations/colors.html | #F5F6F8 (card must match) |
/design-sync step-by-step ​
Who does what:
| Step | Who | Action |
|---|---|---|
| 1 | Claude Code | Edit design-system/*.html cards to match claude-design.md §4 + resolve the token conflict. |
| 2 | Claude Code | In Claude Code, invoke the DesignSync tool (/design-sync). Push each card incrementally. Call register_assets after each batch. |
| 3 | Owner (browser) | Open claude.ai/design → project "Heritage Virginia Community Hub". Reload the page. Verify the published cards reflect the updated palette. |
| 4 | Owner (browser) | Design / iterate screens in the Claude Design canvas. |
| 5 | Owner (browser) | Click "Handoff to Claude Code" on finished prototypes. |
| 6 | Claude Code | Accept the Handoff; real React code lands in packages/ui/src/ (and apps/web / apps/mobile). Review + refine. |
| 7 | Claude Code | Regenerate design-system/*.html cards from real components once packages/ui/src/ is stable. At that point the spec, the Claude Design project, and the shipped library all express one identical system. |
Retired tool ​
Google Stitch was the earlier design-exploration tool. Its files (DESIGN.md, ios-app-screen-prompts.md) live in docs/archive/stitch-prompts/ for reference only. They are not part of the active workflow.
Alternatives considered ​
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Claude Design + @hch/ui (chosen) | Purpose-built design-system tool; Handoff produces real code; keeps design + code in one authoring loop | Requires design-sync discipline; Handoff step is browser-side (owner) | — chosen |
| Figma | Industry-standard; component library ecosystem | Paid ($15+/mo per editor); separate code-generation step; adds a subscription for a ~200-member ministry project | Rejected on cost and operational fit |
| Storybook-only (no design tool) | Already wired in repo; free | No visual canvas; component drift between design intent and implementation; no stakeholder preview | Rejected — Storybook is the component showcase, not the design canvas |
| Tailwind CSS (utility-first, no design tool) | Rapid prototyping; large community | Ties visual language to HTML class names rather than typed tokens; undermines the @hch/ui abstraction boundary | Rejected — does not serve the "import from @hch/ui" principle |
| No design system (ad-hoc CSS/inline styles) | Fastest initial start | Inconsistent UI; no shared language between designer and developer; unscalable for multi-portal app | Rejected — the current state before this ADR; explicitly the problem being closed |
Consequences ​
Positive ​
- A single, typed import (
@hch/ui) is the only way a surface touches the visual language — no palette drift, no duplicated spacing constants. - The Claude Design canvas gives the owner a live preview of screens before any code ships, informed by the same design tokens the app uses.
- The Handoff-to-Claude-Code loop reduces the gap between design intent and implementation.
- One canonical background hex (
#F5F6F8) resolves thetokens.ts/README discrepancy.
Negative / trade-offs ​
- The "Handoff" and "design screen" steps in the loop require the owner to be at
claude.ai/designin a browser. Claude Code handles the/design-syncpush, the token edits, and the Handoff integration, but the design canvas itself is browser-only. - Existing 25+
apps/webpages have inline styles that violate the @hch/ui import rule. Closing this gap is tracked as a task under AB#3237 and will require a deliberate rewrite pass.
Risks ​
- Handoff drift — if "Handoff to Claude Code" produces code that diverges from the
@hch/uicomponent API, the Handoff target needs to bepackages/ui/src/notapps/webdirectly. Mitigation: always route Handoff output throughpackages/ui/src/first. - Card staleness — if
packages/ui/src/components evolve but thedesign-system/*.htmlcards are not regenerated, the Claude Design canvas drifts from the shipped library. Mitigation: the Step 7 card-regeneration loop; enforce in PR review for anytokens.tschange.
References ​
- docs/internal/design/claude-design.md — full workflow + canonical visual spec
- packages/ui/src/tokens.ts — runtime tokens (corrected by this ADR)
- packages/ui/design-system/ — Claude Design sync source
- ADR 0001 — Monorepo + three-layer structure
- ADR 0008 — Platform composition — names
packages/uias the shared package layer - ADO: Feature AB#3160 (Design system), Story AB#3237, Tasks AB#3238, AB#3239, AB#3242, Epic AB#3074