# Design patterns

Which design source, kit and agent skill each surface uses, and the rules that keep a rebrand cheap.

The template's visual identity lives in tokens (`packages/ui/src/styles/globals.css`), and every surface composes from owned source in `packages/ui`. `apps/web/PRODUCT.md` holds the principles and anti-references; read it before any UI work. This page says which tool each job uses, so a project built from the template keeps one design language as it grows.

## Three registers [#three-registers]

| Register       | Surfaces                                | Authority                                                                    | Agent skills                                                       |
| -------------- | --------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Operate        | dashboard, todos, files, settings, auth | shadcn primitives and tokens; `apps/web/PRODUCT.md`                          | `ui-catalog`, `web-design-guidelines`                              |
| Persuade       | landing, marketing, pricing             | `docs/landing.md`; the same tokens through `landing.css` aliases             | `design-taste-frontend`, `redesign-existing-projects`, `stageshow` |
| Developer docs | `/docs`                                 | Fumadocs headless (`fumadocs-core` + `fumadocs-mdx`) with our own components | `docs-and-changelog`                                               |

An agent picks the register first (`ui-catalog` step 1), then loads only that register's skills. Taste guidance never reaches an Operate screen.

## Sources by job [#sources-by-job]

| Job                                       | Source                                                                                                          | Skill                                                     |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| Primitive, form, overlay, empty, toast    | [shadcn/ui](https://ui.shadcn.com), installed in `packages/ui/src/components`                                   | `shadcn`, `ui-catalog`                                    |
| Free composites (tree, timeline, pickers) | [ReUI](https://reui.io) free registry (`@reui`); Pro blocks stay out                                            | `ui-catalog`                                              |
| Chat, streaming, tool cards               | the installed AI/chat set, then [AI Elements](https://elements.ai-sdk.dev)                                      | `ai-assistant`                                            |
| Charts, sparklines, KPI cards             | [Bklit UI](https://bklit.com), the `@bklit` shadcn registry (MIT, source copied into the app)                   | `bklit-ui`, `bklit-ui-design`, `bklit-ui-migrate`         |
| Product illustrations (faux UI)           | [Stageshow](https://benji.org/stageshow), kit vendored in `apps/web/src/platform/stageshow/`                    | `stageshow`                                               |
| Loading states (skeletons)                | [skullet](https://skullet.lochie.me) through `MeasuredSkeleton` (`apps/web/src/platform/skeleton/`) and samples | `ui-catalog`                                              |
| Motion on an existing control             | [transitions.dev](https://transitions.dev) recipes                                                              | `transitions-dev`, `emil-design-eng`, `review-animations` |
| Native (Expo)                             | SwiftUI / Jetpack Compose through `@expo/ui`                                                                    | `expo-ui`, `expo-native-ui`                               |

## Three rules with no alternative [#three-rules-with-no-alternative]

- **Charts come from Bklit UI only.** Every chart, sparkline and KPI or stat card on the web is a Bklit component, installed with `pnpm dlx shadcn@latest add @bklit/<item>` in `packages/ui`. No other chart library (Recharts, Chart.js, Nivo, Victory, visx, ECharts, ApexCharts, Highcharts, Tremor and the like) is imported in `apps/web/src` or `packages/ui/src` or listed in their `package.json`; `scripts/web-chart-library.test.mjs` fails if one is. d3 stays allowed, because Bklit components call it. An existing chart moves over with `bklit-ui-migrate`.
- **Product illustrations are built with Stageshow only.** Faux product UI on landing, marketing and docs pages (stills, feature cards, cursor demos, bento grids) starts from a Stageshow gold example, uses the kit in `apps/web/src/platform/stageshow/` (copy more parts from `.agents/skills/stageshow/kit` when a scene needs them) and ships only after its loop passes `film.mjs --gate`. A chart inside a scene is a Bklit chart placed on the stage, since Stageshow takes the app's own components. Review holds this rule.
- **Loading skeletons are skullet, through `MeasuredSkeleton` only.** Every loading skeleton in the web app is `MeasuredSkeleton` from `@/platform/skeleton/measured-skeleton`: pass `data` and a `sample` of the same shape from the feature's `<feature>.samples.ts` and render from the argument of `children`, or pass `loading` over content already on screen (a refetch). The wrapper adds the translated label; the provider in the signed-in layout sets the reveal and the theme, and the colours are the `--skullet-*` tokens. Only `apps/web/src/platform/skeleton/` imports `skullet`. No hand-made `animate-pulse` boxes, no shadcn `Skeleton` or `SidebarMenuSkeleton`, and no other skeleton library (react-loading-skeleton, react-content-loader, MUI, Chakra and the like) in `apps/web/src` or `packages/ui/src` or their `package.json`. Skeletons render only behind sign-in, so the skullet chunk stays off public pages ([W15](/docs/development/web-conventions)): a public page renders its data from the route loader, or a `Spinner` for a pending action. `scripts/web-skeletons.test.mjs` checks all of this, including that no route outside `routes/_auth/` reaches the wrapper through its imports.

Bklit draws DOM and SVG for React on the web and has no React Native build, so the chart rule stops at `apps/web` and `packages/ui`. A chart in the Expo app uses [`victory-native`](https://commerce.nearform.com/open-source/victory-native), which draws with Skia, Reanimated and Gesture Handler, all already in `apps/mobile`; add it with `expo install` with the first mobile chart (it needs a newer Skia than the app pins today). An iOS-only screen may use `Chart` from `@expo/ui/swift-ui` (Swift Charts) instead.

Skullet measures the DOM and ships React, Vue, Svelte and web-component builds, but none for React Native, so the skeleton rule stops at the web too. A loading list in the Expo app uses `SkeletonRows` from `apps/mobile/src/design` (a still placeholder with the metrics of a `SettingRow` group and a spoken label).

The shadcn `skeleton.tsx` stays in `packages/ui` because the vendored shadcn sidebar (`SidebarMenuSkeleton`) and the ReUI emoji picker draw their own loading state with it; app code does not import either. The wrapper covers skullet's `Skeleton` only: a Suspense fallback (`SkeletonSuspense`) or a placeholder box (`SkeletonBox`) gets its own wrapper in `platform/skeleton/` when a screen first needs one, never a direct import. Passing `id` keeps a skeleton's shape in `localStorage`; the change that first does so also calls `forgetShapes()` on sign-out.

Two surfaces predate these rules: the dashboard KPI cards (`dashboard.stat-card.tsx`, plain number cards) and the hand-built fragments of the landing feature board (`landing.feature-*.tsx`). Do not copy them as patterns; move them to Bklit and Stageshow when you rework them.

## Rules that keep it consistent [#rules-that-keep-it-consistent]

- **Tokens, not values.** A component reads `--primary`, `--card`, `--radius` and the rest; a registry's own colors and fonts are mapped onto them, never copied. A kit with its own token layer (Stageshow's `--ss-*`, Bklit's chart colors) is aliased to ours in one place.
- **Owned source.** Registry items are copied into `packages/ui` (or a feature's `platform/` folder) and exported one subpath at a time. Diff before accepting an overwrite: local files carry React Compiler and test edits.
- **Every state ships.** Loading, empty and error are part of the screen, because developers copy what the template shows.
- **A skeleton is drawn from the real component.** A loading state is measured from a sample of the component it stands in for (`<feature>.samples.ts`), never hand-built from boxes, so nothing moves when the data lands.
- **Honest illustrations.** Faux UI on landing, marketing and docs pages uses the product's own strings from the locale catalogs, shows only features the template has, and is labelled as an illustration. A Stageshow scene passes `film.mjs --gate` before it ships.
- **Motion respects the reader.** Every animation has a reduced-motion state, pauses off screen, and starts when it can be seen.
- **Weight is measured.** A new kit or registry item records its gzip delta on the route it lands in, in `docs/measurements/`.

## Key files [#key-files]

- [`apps/web/PRODUCT.md`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/apps/web/PRODUCT.md)
- [`docs/landing.md`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/docs/landing.md)
- [`packages/ui/components.json`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/packages/ui/components.json)
- [`.agents/skills/ui-catalog/SKILL.md`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/.agents/skills/ui-catalog/SKILL.md)
- [`scripts/web-chart-library.test.mjs`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/scripts/web-chart-library.test.mjs)
- [`apps/web/src/platform/skeleton/measured-skeleton.tsx`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/apps/web/src/platform/skeleton/measured-skeleton.tsx)
- [`scripts/web-skeletons.test.mjs`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/scripts/web-skeletons.test.mjs)

