Skip to content

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:

  1. No ADR for the design tool. docs/internal/design/claude-design.md documents 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.
  2. 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").
  3. A token discrepancy. The canonical spec in claude-design.md §4 gives the app background as #F5F6F8 (Quiet Mist Gray); packages/ui/src/tokens.ts and 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. Every apps/web and apps/mobile screen 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 styles
  • claude-design.md §4 is 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, call register_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/web and apps/mobile import exclusively from @hch/ui. No surface holds its own palette constants, spacing magic numbers, or primitive components.

The @hch/ui import rule (hard constraint) &ZeroWidthSpace;

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 &ZeroWidthSpace;

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:

LocationCorrect 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 &ZeroWidthSpace;

Who does what:

StepWhoAction
1Claude CodeEdit design-system/*.html cards to match claude-design.md §4 + resolve the token conflict.
2Claude CodeIn Claude Code, invoke the DesignSync tool (/design-sync). Push each card incrementally. Call register_assets after each batch.
3Owner (browser)Open claude.ai/design → project "Heritage Virginia Community Hub". Reload the page. Verify the published cards reflect the updated palette.
4Owner (browser)Design / iterate screens in the Claude Design canvas.
5Owner (browser)Click "Handoff to Claude Code" on finished prototypes.
6Claude CodeAccept the Handoff; real React code lands in packages/ui/src/ (and apps/web / apps/mobile). Review + refine.
7Claude CodeRegenerate 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 &ZeroWidthSpace;

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 &ZeroWidthSpace;

OptionProsConsWhy not chosen
Claude Design + @hch/ui (chosen)Purpose-built design-system tool; Handoff produces real code; keeps design + code in one authoring loopRequires design-sync discipline; Handoff step is browser-side (owner)— chosen
FigmaIndustry-standard; component library ecosystemPaid ($15+/mo per editor); separate code-generation step; adds a subscription for a ~200-member ministry projectRejected on cost and operational fit
Storybook-only (no design tool)Already wired in repo; freeNo visual canvas; component drift between design intent and implementation; no stakeholder previewRejected — Storybook is the component showcase, not the design canvas
Tailwind CSS (utility-first, no design tool)Rapid prototyping; large communityTies visual language to HTML class names rather than typed tokens; undermines the @hch/ui abstraction boundaryRejected — does not serve the "import from @hch/ui" principle
No design system (ad-hoc CSS/inline styles)Fastest initial startInconsistent UI; no shared language between designer and developer; unscalable for multi-portal appRejected — the current state before this ADR; explicitly the problem being closed

Consequences &ZeroWidthSpace;

Positive &ZeroWidthSpace;

  • 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 the tokens.ts/README discrepancy.

Negative / trade-offs &ZeroWidthSpace;

  • The "Handoff" and "design screen" steps in the loop require the owner to be at claude.ai/design in a browser. Claude Code handles the /design-sync push, the token edits, and the Handoff integration, but the design canvas itself is browser-only.
  • Existing 25+ apps/web pages 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 &ZeroWidthSpace;

  • Handoff drift — if "Handoff to Claude Code" produces code that diverges from the @hch/ui component API, the Handoff target needs to be packages/ui/src/ not apps/web directly. Mitigation: always route Handoff output through packages/ui/src/ first.
  • Card staleness — if packages/ui/src/ components evolve but the design-system/*.html cards are not regenerated, the Claude Design canvas drifts from the shipped library. Mitigation: the Step 7 card-regeneration loop; enforce in PR review for any tokens.ts change.

References &ZeroWidthSpace;

Heritage Community Hub — Internal. Access restricted via Cloudflare Access + Entra ID.