diff --git a/CLAUDE.md b/CLAUDE.md
index 1ffa40c..d6ef0ab 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -48,7 +48,7 @@ When instruction files disagree: the area file (`apps/*/CLAUDE.md`, `db/CLAUDE.m
### Frontend
-**Frontend** — store / services / pages / components layering with consistent loading/error/empty/success states and reuse of base UI primitives. **Read `apps/frontend/CLAUDE.md` before touching `apps/frontend/`.**
+**Frontend** — store / services / pages / components layering with consistent loading/error/empty/success states and reuse of base UI primitives. **Read `apps/frontend/CLAUDE.md` before touching `apps/frontend/`.** On a new project, agree the UI component approach with the user before any screen or component is built (that file → *UI component approach*).
### UI mockup / design reference
@@ -159,6 +159,6 @@ Where `main` is protected (Day-1 step 11) or the work is spec-backed (`specs/REA
## Learnings
-Durable, cross-session notes go here instead of the memory system (see the note at the top of this file). Keep each entry to a line or two — what was learned and how to apply it. At instantiation the usual first entry is the stack-pack choice (see the Day-1 checklist in `README.md`).
+Durable, cross-session notes go here instead of the memory system (see the note at the top of this file). Keep each entry to a line or two — what was learned and how to apply it. At instantiation the usual first entries are the stack-pack choice and the UI component approach the user agreed (`apps/frontend/CLAUDE.md` → *UI component approach*); see the Day-1 checklist in `README.md`.
- Spec tool: none by default. Spec Kit was removed on 2026-07-29; adopt a tool per `specs/README.md` if wanted. Feature numbers 001–004 are consumed and must not be reused: 001 design guide (retired), 002 repo-guidance audit (retired, no directory), 003–004 seo (retired, artifacts deleted).
diff --git a/README.md b/README.md
index b83d4c4..6f7d463 100644
--- a/README.md
+++ b/README.md
@@ -146,9 +146,19 @@ Complete this once when creating a project.
- Add `.github/workflows/deploy.yml` only when the chosen pack requires it and the deployment
target is configured.
- Add a real `.env.example`.
-6. **Set the visual baseline.** Declare the primary form factor in `apps/frontend/CLAUDE.md`.
- Rebrand `design/tokens.css`, open `design/design-guide.html`, and confirm the visual system
- before building screens.
+6. **Set the visual baseline.** Complete these in order, before any screen work starts.
+ - Declare the primary form factor in `apps/frontend/CLAUDE.md`.
+ - **Agree the UI component approach with the user** — headless primitives with an own styled
+ layer, a copy-in component set, or a styled kit
+ ([`apps/frontend/CLAUDE.md`](apps/frontend/CLAUDE.md) → *UI component approach*). Never pick
+ it silently. Record the answer under **Learnings**.
+ - Rebrand `design/tokens.css`.
+ - Reconcile `design/design-guide.html` with the agreed approach
+ (→ *Hydrating the design guide*), then hydrate its *Components & reuse* chapter with real
+ specimens as shared primitives are built.
+ - **Open `design/design-guide.html` in a browser and have the user review it.** The visual
+ system is confirmed from the rendered page, never from the source or a diff. Record the
+ sign-off under **Learnings**.
7. **Add runtime configuration.** Restore the local environment and secrets. Stand up staging when
the chosen pack defines one.
8. **Run the complete suite.** Push the configured project and confirm that its first CI run is
diff --git a/apps/frontend/CLAUDE.md b/apps/frontend/CLAUDE.md
index 3a7f61d..4b5ce0b 100644
--- a/apps/frontend/CLAUDE.md
+++ b/apps/frontend/CLAUDE.md
@@ -55,11 +55,23 @@ A route is part of the app's public contract; a file path is an implementation d
## Design guide — the visual keystone (confirm before building UI)
-The project's visual system lives in the design guide (`design/design-guide.html`): the design principles, every foundation (colour, type, spacing, layout, shape, surfaces, motion, iconography, states/focus, accessibility, content, data formatting), and the composition chapters (screen archetypes, tables & grids, forms, view states & feedback) — rendered live from the single token source (`design/tokens.css`, the seed for the app's `tokens.`). Foundations only, by design: components stay flexible per app and are built from these foundations.
+The project's visual system lives in the design guide (`design/design-guide.html`): the design principles, every foundation (colour, type, spacing, layout, shape, surfaces, motion, iconography, states/focus, accessibility, content, data formatting), and the composition chapters (screen archetypes, tables & grids, forms, view states & feedback) — rendered live from the single token source (`design/tokens.css`, the seed for the app's `tokens.`). It ships with foundations only, because a template cannot know a project's components. A project hydrates it: see *Hydrating the design guide* below.
- **The guide is a gate.** For a new project or a rebrand, no screen or component work starts until the guide reflects the brand, has been reviewed in a browser, and is signed off — record the sign-off as a line in root `CLAUDE.md` **Learnings** (who confirmed, guide version/date). Once the system is established, small additions don't re-gate — but a new foundational token lands in the guide first.
- **Customise by editing tokens, not screens.** A rebrand edits the primitive token tier; the semantic tier and the whole guide re-derive from it.
-- **The guide binds components without prescribing them.** Every component consumes semantic tokens, follows the guide's state ladder and focus spec, and meets its accessibility floor. A pattern that recurs across projects earns a specimen in the guide; a stack pack may add a Storybook against the same tokens.
+- **The guide binds components without prescribing them.** Every component consumes semantic tokens, follows the guide's state ladder and focus spec, and meets its accessibility floor. Within a project, every shared primitive earns a specimen in that project's guide (see *Hydrating the design guide*). A pattern that recurs across *projects* is promoted back into the template's guide.
+
+### Hydrating the design guide
+
+The guide arrives as foundations and is filled in per project, in two moves.
+
+**1. Reconcile the guide with the chosen approach.** Once *UI component approach* is settled, re-read the sections that describe how components look and behave: states & focus, shape & border, surfaces & elevation, motion, density, tables & grids, forms, view states. A styled kit arrives with its own answers to several of them. For each mismatch, either theme the kit onto the guide's answer or amend the guide to the kit's. This feeds the sign-off in *The guide is a gate*; it is not a second approval step.
+
+**2. Populate *Components & reuse* with the project's real components.** As each shared primitive lands in `atoms/` or `molecules/`, add a specimen to the guide: the component rendered from the project's own tokens, with its variants and states. The guide then answers rung 3 of its own reuse ladder ("does the app already have it?") instead of sending the reader to the code. Update the specimen in the same change that adds or alters a primitive; a specimen that has drifted from the app is worse than no specimen. Organisms are feature-specific and stay out of the guide.
+
+**Open the guide for the user whenever either move finishes.** Launch the rendered `design/design-guide.html` in a browser and say what changed. The guide is reviewed as a rendered page, never as source or a diff — a token that resolves wrong, a specimen that has drifted, and a broken state ladder are all invisible in a diff. Do not treat a hydration or reconciliation as complete until the user has seen it.
+
+A stack pack may bind move 2 to a component workshop (Storybook or equivalent) rendered against the same tokens. The guide remains the single reviewable page either way.
**Never-violate gates** (the named guide chapter is canonical):
@@ -70,6 +82,27 @@ The project's visual system lives in the design guide (`design/design-guide.html
5. One density app-wide, set at the token layer — never mixed within a page (guide → *Screen archetypes*).
6. Tables, forms, and view states follow the composition patterns — the pattern outranks the component library's defaults (guide → *Tables & grids*, *Forms*, *View states & feedback*). A working table ships the standard kit (search, sort, column filters, pagination, column customisation, selection) by default; dropping a capability is the recorded decision.
+## UI component approach (decide with the user before building UI)
+
+How much of the component layer the project writes is a Day-1 decision, and it belongs to the user. **Ask before the first screen or component is built; never pick silently.** Record the answer as a line in root `CLAUDE.md` **Learnings**: which option, one sentence of reasoning, the date. Then reconcile the design guide with the answer and get it accepted before screen work starts (*Hydrating the design guide* above).
+
+Three options:
+
+1. **Headless primitives with an own styled layer.** Unstyled behavioural primitives (Radix or equivalent) wrapped as `atoms/` that map the project's tokens. Every stack pack binds this by default. It suits a project with its own design guide and token set, which is this template's normal case.
+2. **Copy-in component set.** A generator (shadcn/ui or equivalent) writes headless-plus-styling files into `atoms/`, which the project then owns and edits. Same dependencies as option 1, with the code written for you, at the cost of adopting the generator's token naming.
+3. **Styled component kit.** A themed library (MUI, Mantine, Chakra, Ant) supplies finished components. It suits an internal or admin tool where shipping speed outranks brand fidelity, or a project with no bespoke design guide.
+
+**The deciding question is how far the project's design guide sits from the kit's defaults.** A styled kit ships its own colour ramp, spacing scale, and component appearance. Where the guide is close to those, option 3 removes real work. Where the guide is bespoke, the project overrides the kit on every screen, which is more work than writing the styled layer and harder to review. Measure rather than assume: a hand-written `atoms/` tier usually runs a few hundred lines in total, well under the size of a single feature's organisms.
+
+Whichever option is chosen:
+
+- **Take behaviour from a library and write only the appearance.** Focus management, keyboard handling, widget ARIA, table state, and date and timezone maths come from a library (root `CLAUDE.md` → *Don't reinvent existing solutions*). Spacing, colour, and variants come from the project's tokens.
+- **Never mix two foundations.** One library owns a given widget; a second library for the same widget is a defect.
+- **The design guide still gates the work.** A styled kit is themed onto the project's tokens; it is never accepted at its own defaults.
+- **Options 1 and 2 leave *Component structure* as written.** Option 3 substitutes the kit for the headless foundation named there, keeps the atomic tiers and the DRY gate unchanged, and is a deviation from the adopted stack pack's UI binding — say so in the same Learnings line.
+
+**Add a specialised library when a need is behavioural and hard, whichever option is in force.** A headless table library once a table needs sort, filter, pagination, column visibility, or selection (the standard kit in *Design guide* gate 6). A date library at the first formatting or timezone need. Neither depends on the choice above.
+
## Page layout & design tokens
Two things make every screen feel like one product: a single shared layout and a single token source. A page author composes the layout and reaches for tokens — and never re-decides spacing, colour, or navigation.
@@ -124,7 +157,7 @@ Tokens say where values come from; this says which values are good. Checkable pe
## Interaction feedback & perceived performance
-- **Every actionable control shows its state from tokens.** Pressed/active, focus-visible, and disabled states live on the shared `atoms/`/`molecules/` primitives, not per page. Hover is a pointer-device affordance; on touch, the pressed state carries the feedback — never leave a tap without visible response. Surface the headless foundation's focus-visible; don't suppress it.
+- **Every actionable control shows its state from tokens.** Pressed/active, focus-visible, and disabled states live on the shared `atoms/`/`molecules/` primitives, not per page. Hover is a pointer-device affordance; on touch, the pressed state carries the feedback — never leave a tap without visible response. Surface the UI foundation's focus-visible; don't suppress it.
- **In-flight feedback stays on the control that triggered the action.** Disable the control and show an inline busy indicator there — never blank the whole screen for a local action. Full-screen/section loading is for a screen's initial data fetch only (the `loading` state above).
- **Prefer optimistic updates for low-risk mutations** (toggles, reorders, favourites) with rollback and an error message on failure; reserve blocking spinners for genuinely blocking waits.
- **Initial content load uses skeletons that match the final layout;** short indeterminate waits use a spinner. Don't layout-shift from spinner to content.
@@ -149,7 +182,7 @@ Tokens say where values come from; this says which values are good. Checkable pe
## Component structure — atomic design
-Consistency comes from reuse, not per-screen discipline. Every component sits in one of five atomic tiers, built over a headless foundation you never skip — the foundation is a dependency, not a folder: unstyled behavioural primitives from a headless UI library that solve focus management, keyboard handling, and widget-level ARIA for the components routed through it (not page-level a11y; see *Accessibility baseline*).
+Consistency comes from reuse, not per-screen discipline. Every component sits in one of five atomic tiers, built over the UI foundation chosen in *UI component approach*. The foundation is a dependency, not a folder, and it is never skipped: it solves focus management, keyboard handling, and widget-level ARIA for the components routed through it (not page-level a11y; see *Accessibility baseline*). Under options 1 and 2 that foundation is an unstyled headless library; under option 3 it is the styled kit.
1. **Atoms** (`components/atoms/`) — the smallest primitives, each mapping the project's tokens onto the foundation: `
Twice is a coincidence, thrice is a pattern. Recurs across features →
- promote into the app's atoms/. Recurs across projects → earn a specimen here.
+ promote into the app's atoms/, and add its specimen above in the same change.
+ Recurs across projects → promote it back into the template's guide.
The guide grows from evidence, not speculation.
diff --git a/stacks/django/frontend.md b/stacks/django/frontend.md
index 342b39e..dfbc087 100644
--- a/stacks/django/frontend.md
+++ b/stacks/django/frontend.md
@@ -13,7 +13,7 @@ Bind the frontend to React, TypeScript, Vite, React Router, and the Django API.
| Data | relative `/api` calls through `services/http.ts` |
| Contract | drf-spectacular OpenAPI to openapi-typescript |
| State | React Context by domain |
-| Styling | Tailwind CSS 4 and Radix UI |
+| Styling | Tailwind CSS 4; UI foundation per the base's *UI component approach* (this pack defaults to Radix UI) |
| Tests | Vitest, React Testing Library, Playwright |
Everything exposed through `VITE_*` is public. Parse frontend configuration once with Zod.
@@ -40,7 +40,8 @@ Everything exposed through `VITE_*` is public. Parse frontend configuration once
## UI, version, and testing
-- Declare tokens through Tailwind 4 `@theme` and wrap Radix primitives as atoms.
+- Declare tokens through Tailwind 4 `@theme`.
+- Wrap the UI foundation chosen in the base's *UI component approach* as atoms; default that choice to Radix primitives.
- Self-host fonts and keep one `cn()` helper.
- Inject and render `VITE_APP_VERSION`.
- Serve `version.json` with `no-store` and show a dismissible refresh prompt when it changes.
diff --git a/stacks/enterprise/frontend.md b/stacks/enterprise/frontend.md
index d682360..42210c4 100644
--- a/stacks/enterprise/frontend.md
+++ b/stacks/enterprise/frontend.md
@@ -45,10 +45,10 @@ Bind the frontend to server-first Next.js App Router. Use JavaScript or TypeScri
## UI bindings
-- Wrap Radix UI primitives as atoms.
+- Wrap the UI foundation chosen in the base's *UI component approach* as atoms; default that choice to Radix UI primitives.
- Keep interactive primitives as client leaves.
- Use CSS variables, Tailwind, or a static token module that Server Components can consume.
-- Do not use runtime styling that forces the token boundary into a Client Component.
+- Do not use runtime styling that forces the token boundary into a Client Component. This rules out a styled kit built on runtime CSS-in-JS; a kit that compiles to static CSS is fine.
- Pass only React-serialisable values into client leaves.
- Format dates on one side of the boundary with an explicit locale and timezone.
- Move focus to the main landmark and announce route changes.
diff --git a/stacks/mern/frontend.md b/stacks/mern/frontend.md
index 4f7ff7d..8fa99eb 100644
--- a/stacks/mern/frontend.md
+++ b/stacks/mern/frontend.md
@@ -12,7 +12,7 @@ Bind the frontend to React, TypeScript, Vite, React Router, and a platform-neutr
| Routing | React Router library mode in `src/routes.tsx` |
| Data | relative `/api` calls through `services/http.ts` |
| State | one React Context provider per domain |
-| Styling | Tailwind CSS 4, Radix UI, CSS-variable tokens |
+| Styling | Tailwind CSS 4, CSS-variable tokens; UI foundation per the base's *UI component approach* (this pack defaults to Radix UI) |
| Configuration | Zod-parsed `VITE_*` values |
| Tests | Vitest, React Testing Library, Playwright |
@@ -44,7 +44,8 @@ The deployment platform must provide:
## UI, version, and testing
- Map router pending, error, empty, and missing-resource states to the shared primitives.
-- Declare tokens through Tailwind 4 `@theme` and wrap Radix primitives as atoms.
+- Declare tokens through Tailwind 4 `@theme`.
+- Wrap the UI foundation chosen in the base's *UI component approach* as atoms; default that choice to Radix primitives.
- Use mobile-first utilities, container queries for components, and one shared gutter primitive.
- Inject `VITE_APP_VERSION`, render it, and poll `version.json` for a dismissible refresh prompt.
- Offer an explicit reload when an old lazy chunk no longer exists.
diff --git a/stacks/vercel-csr/frontend.md b/stacks/vercel-csr/frontend.md
index 03a96b2..85006e5 100644
--- a/stacks/vercel-csr/frontend.md
+++ b/stacks/vercel-csr/frontend.md
@@ -12,7 +12,7 @@ Bind the frontend to React 19, TypeScript, Vite 7, React Router, and Vercel's st
| Routing | React Router library mode in `src/routes.tsx` |
| Data | relative `/api` calls through `services/http.ts` |
| State | one React Context provider per domain |
-| Styling | Tailwind CSS 4, Radix UI, CSS-variable tokens |
+| Styling | Tailwind CSS 4, CSS-variable tokens; UI foundation per the base's *UI component approach* (this pack defaults to Radix UI) |
| Configuration | Zod-parsed `VITE_*` values in `src/lib/env.ts` |
| Tests | TypeScript, Vite build, Playwright |
@@ -50,7 +50,8 @@ Everything exposed through `VITE_*` is public. Never put a secret in the fronten
- Map route failures to `errorElement` and surface the correlation ID.
- Use the shared empty and not-found components.
- Declare semantic tokens through Tailwind 4 `@theme`.
-- Wrap Radix primitives as atoms. Use `class-variance-authority`, one `cn()` helper, and `lucide-react`.
+- Wrap the UI foundation chosen in the base's *UI component approach* as atoms.
+- Default that choice to Radix primitives with `class-variance-authority`, one `cn()` helper, and `lucide-react`.
- Self-host fonts with `font-display: swap`.
- Use mobile-first utilities, container queries for reusable components, and intrinsic layout before new breakpoints.
- Keep page gutters in one `` or `` primitive.
diff --git a/stacks/vercel-ssr/frontend.md b/stacks/vercel-ssr/frontend.md
index 3b3484b..5d1a082 100644
--- a/stacks/vercel-ssr/frontend.md
+++ b/stacks/vercel-ssr/frontend.md
@@ -12,7 +12,7 @@ Bind the frontend to TypeScript, Next.js App Router, Server Components, and Serv
| Reads | server module `controller/queries.ts` |
| Mutations | server module `controller/actions.ts` |
| Client state | React Context only for shared interactive state |
-| Styling | Tailwind CSS 4, Radix UI, `next/font` |
+| Styling | Tailwind CSS 4, `next/font`; UI foundation per the base's *UI component approach* (this pack defaults to Radix UI) |
| Tests | TypeScript, Next build, Vitest as needed, Playwright |
Default every file to a Server Component. Add `'use client'` only at the smallest interactive leaf.
@@ -45,8 +45,9 @@ Unauthenticated queries and actions redirect to login while preserving the inten
## UI bindings
- Declare tokens through Tailwind 4 `@theme`.
-- Wrap Radix primitives as atoms and keep client directives at the interactive leaf.
-- Use `class-variance-authority`, one `cn()` helper, `lucide-react`, and `next/font`.
+- Wrap the UI foundation chosen in the base's *UI component approach* as atoms, keeping client directives at the interactive leaf.
+- Default that choice to Radix primitives with `class-variance-authority`, one `cn()` helper, `lucide-react`, and `next/font`.
+- Reject a styled kit that requires runtime CSS-in-JS: it pulls the token boundary into a Client Component. A kit that compiles to static CSS is fine.
- Use mobile-first utilities, container queries for reusable components, and intrinsic layout before new breakpoints.
- Keep page gutters in one shared layout primitive.
- Use `next/image` with explicit `sizes` and an aspect-ratio wrapper.