<!-- source: openspec/current/design-components.md -->

---
title: Design Components
status: stable
owner: pavel
audience: [pavel, ai, engineer]
tldr: Component-intent spec for the studio design system — per-primitive anatomy, closed variant lists with rationale, a11y baseline, status, and the R10 promotion criteria.
canonical_at: openspec/brands/studio/design-components.md
---
# Design Components — capability spec

> The component-intent layer of the studio design system. It owns *what each primitive means* — anatomy, closed variant lists with rationale, per-component a11y baseline, `status`, and `lives_in`. It does **not** own props APIs, Tailwind class strings, or `path:line` anchors; those live in [`ui.md`](ui.md). When an entry needs an implementation path, it says "see `ui.md`" rather than embedding the path.
>
> **Sister files:** [`design-system.md`](design-system.md) (visual intent) · [`design-tokens.md`](design-tokens.md) (token registry) · [`design-motion.md`](design-motion.md) (motion) · [`ui.md`](ui.md) (implementation references).
>
> Authority: rules R3 (intent ≠ implementation), R5 (closed variant lists with rationale), R10 (promotion criteria) from [`_methodology/design-system-convention.md`](../_methodology/design-system-convention.md).

---

## 1. How to read a component entry

Each component entry carries:

- **Name** — the exported component name.
- **`status`** — one of `canonical` / `deferred` / `experimental` / `deprecated`.
- **`lives_in`** — the package or app that owns the source (`packages/ui`, `apps/studio`, `apps/web`, `packages/legacy-brand`).
- **Anatomy** — the rows, regions, and slots the component composes from.
- **Variants** — a *closed* list; every variant named with one sentence of "when used" rationale (R5). Open-ended variant systems are forbidden.
- **A11y baseline** — the keyboard / screen-reader / focus story for the component, conforming to the corpus-wide WCAG 2.2 AA contract in [`design-system.md § 5`](design-system.md).
- **Token map** — which token groups in [`design-tokens.md`](design-tokens.md) the component consumes.

The `path:line` anchor to the implementing file lives in [`ui.md § Implementation references`](ui.md), not here (R3).

## 2. Primitive anatomy

Atomic form and content primitives, built directly on tokens.

- **`Button`** — `status: canonical`, `lives_in: packages/ui`. Anatomy: label + optional leading/trailing icon slot. Variants: **Primary** (the main action on a surface), **Secondary** (a peer action of lower weight), **Accent** (a tactical/terracotta-weighted action), **Ghost** (a low-chrome action that reads as a link), **Mono** (a monospace technical action — used in workspace chrome). A11y baseline: native `<button>` semantics, visible `:focus-visible` ring, target meets the 24×24 floor.
- **`Input`** — `status: canonical`, `lives_in: packages/ui`. Anatomy: single-line field. Sans-serif body. A11y baseline: paired with a `<Label>`; focus ring; error state announced, not colour-only.
- **`Textarea`** — `status: canonical`, `lives_in: packages/ui`. Anatomy: multi-line field, serif body (matches reading-first long-form input).
- **`Label`** — `status: canonical`, `lives_in: packages/ui`. Anatomy: form-control label; associates with its field.
- **`Select`** — `status: canonical`, `lives_in: packages/ui`. Anatomy: trigger + popover list. Radix-backed for keyboard + ARIA.
- **`Form`** — `status: canonical`, `lives_in: packages/ui`. Composition wrapper for field groups.
- **`Avatar`** — `status: canonical`, `lives_in: packages/ui`. Anatomy: image with text fallback.
- **`Badge`** — `status: canonical`, `lives_in: packages/ui`. Anatomy: small inline status/label marker.
- **`Separator`** — `status: canonical`, `lives_in: packages/ui`. Anatomy: a thin rule; horizontal or vertical.
- **`Skeleton`** — `status: canonical`, `lives_in: packages/ui`. Anatomy: a low-key loading placeholder block — the quiet loading pattern from [`design-system.md § Voice & casing`](design-system.md).
- **Overlay primitives** — `Dialog`, `AlertDialog`, `Sheet`, `DropdownMenu`, `Tooltip`, `Collapsible`, `Popover`: `status: canonical`, `lives_in: packages/ui`. Radix-backed. The URL-state overlay system (`OverlayHost`, `defineDialog`, `useDialog`) is specified in [`linkable-ui.md`](linkable-ui.md).
- **`Tabs`** — `status: canonical`, `lives_in: packages/ui`. Variants: **default**, **line** (an underline-marked tab strip), **strip** (a denser segmented strip).
- **`Card`** — `status: canonical`, `lives_in: packages/ui`. Variants: **default** (a plain content surface), **tactical** (a corner-bracketed surface — the studio's signature framed card).
- **`Sonner`** — `status: canonical`, `lives_in: packages/ui`. The toaster; announcements route through the singleton `announce()`.

## 3. Layout primitives

The four layout primitives shipped in RAP-211 (`ui.md § Known gaps` — "Layout primitives missing" closed). All `status: canonical`, `lives_in: packages/ui`.

- **`Container`** — page-width wrapper. Variants: **reading** (~56ch character-based reading column), **wide** (1200px page max), **full** (no max-width). Consumes the `--width-*` layout-dimension tokens.
- **`Stack`** — flex-flow primitive; gap from the `--space-*` scale.
- **`Grid`** — CSS-grid wrapper; collapses to a single column on mobile.
- **`Spacer`** — flex-grow filler, or a fixed-token decorative gap.

## 4. Tactical primitives

The brand's structural grammar. All `status: canonical`, `lives_in: packages/ui`.

- **`TacticalFrame`** — the composite four-bracket frame. Anatomy: four corner brackets wrapping arbitrary children. The studio's signature "engineered surface" motif. Consumes `--bracket-size` / `--bracket-weight`.
- **`DividerTechnical`** — a horizontal or vertical rule with tactical tick marks.

## 5. Chip family

The unified `Chip` primitive (RAP-223) replaced six parallel pill components with one primitive plus thin facades.

- **`Chip`** — `status: canonical`, `lives_in: packages/ui`. The single chip primitive. Variants (the closed `kind` list, seven kinds): **status** (lifecycle state), **tool-status** (per-tool execution state), **smart** (a composer quick-action), **tech** (an inline code/tech tag), **skill** (a geo-marker / segment tag — used by the home audience-tag system), **persona** (a bracketed-letter identity glyph), **mono-label** (an uppercase mono eyebrow). A11y baseline: informational, never colour-only; status meaning carried in text.
- **Thin facades** — `StatusPill`, `MonoLabel`, `TechPill`, `SmartPill`, `PersonaBadge`, `ToolStatusPill`: `status: canonical`, `lives_in: packages/ui`. Each delegates to `<Chip kind="…">` for visual DNA; they exist as named, ergonomic entry points. `StatusPill` exposes the six lifecycle visual roles via CVA.

## 6. Studio / workspace primitives

Components for the dark-theme studio workspace. All `status: canonical`, `lives_in: packages/ui` unless noted.

- **`Sidebar`** — the generic vertical-rail / panel primitive. Anatomy: `SidebarProvider` + `Sidebar` + `SidebarInset`; `SidebarHeader` / `SidebarFooter` slots. The single left surface of the workspace chrome (the parallel `ModeRail` and `AppShell` were removed in RAP-197).
- **`KpiTile`** — a dashboard metric tile.
- **`DataTable<T>`** — a generic table primitive. Dense rows; the corpus 24×24 touch-target floor (not the elective 44×44) is what keeps dense rows viable — see [`design-system.md § 5.2`](design-system.md).
- **`SectionHead`** — a three-row eyebrow + heading + lede primitive.
- **`MetaBar`** — an inline meta-data bar.
- **`StudioLogo`** — `status: canonical`, `lives_in: packages/ui`. The brand wordmark/glyph primitive; a five-variant four-axis API (`variant` × `size` × `tone` × `motion`). Variants include **wordmark**, **glyph**, **mark**. Supersedes `brand-mark.tsx` (see § 11).

## 7. Forge primitives

Three primitives supporting the Forge UI surface. All `status: canonical`, `lives_in: packages/ui`.

- **`PhasePipeline`** — a vertical accordion of exactly four fixed phases (`plan` / `implement` / `review` / `pr`). Anatomy: per-phase header with status pill + optional meta line + collapsible body. Auto-expands the first `running` / `awaiting_input` phase. A11y baseline: Radix `Collapsible`; `aria-expanded` on triggers.
- **`LiveLogStream`** — an SSE-driven monospace log stream. Anatomy: capped in-memory buffer (500 entries, fade marker on drop), auto-scroll with pause-on-hover, jump-to-bottom control. Colour prefix per level (success / info / warning / error) — the level is also in text.
- **`FindingRow`** — a severity-coloured row. Anatomy: severity dot + agent badge + `file:line` + one-line truncated body (expandable) + primary action + overflow menu. Consumes the `--severity-*` token group ([`design-tokens.md § Severity tokens`](design-tokens.md)).

## 8. Compound primitives

Higher-order primitives composed from the atomics. `lives_in: packages/ui`. Default `status: canonical`; experimental entries are flagged inline.

- **`MediaBlock`** — the canonical display primitive for all displayable file content. Anatomy: a MIME-type renderer registry + a universal `SourceView` fallback. Density variants: **compact** (in-chat chrome — body only) and **full** (in-pane chrome — header with title + copy, mode-switch rail, footer). Chrome variants (RAP-1257, D-URC-5/6, 2026-05-29): **`chrome="full"`** (default — current behaviour; the per-density chrome above), **`chrome="minimal"`** (Title-only header, no actions; reserved for future chat-embed surfaces), **`chrome="none"`** (no border, no bg, no Header, no Footer; Body drops its `p-3` so the parent container owns padding edge-to-edge — required when the parent already supplies filename/mode/actions, as in the spec page's `<ChangeRail>`). Mode is controllable via `mode` + `onModeChange` props (parent owns state); default uncontrolled with `defaultMode`. Consumers use `MediaBlock` rather than instantiating `Markdown` / `InlineShiki` / `DiffView` directly.
- **`DocumentShell`** — the long-form document wrapper. Depends on the `@tailwindcss/typography` plugin for `.prose` styling. See [`ui.md § DocumentShell subpath`](ui.md) for the import surface.
- **`chat` primitive** — the chat surface primitive (`packages/ui/src/components/chat/`). Anatomy: `MessageList` + `Message` + `Composer` + `ContentBlock`. Message lifecycle status (`submitted` / `streaming` / `ready` / `error`); content-block kinds include `thinking` and `reasoning`. Composer/list affordances: `ScrollToBottomPill`, `SuggestionRow`, `DateSeparator`, sibling-generation chevrons. A11y baseline: live announcements route through the singleton `announce()` — no direct `aria-live` regions.
- **`SpecNavigator`** — the spec-navigation compound primitive (archive `2026-05-21-add-spec-navigator`).
- **`NavigationMenu`** — the studio-skinned navigation-bar primitive (`packages/ui/src/composed/navigation-menu/`). Anatomy: `NavigationMenu` (root with `activePath` + `indicator` props) + `NavigationMenuList` + `NavigationMenuItem` (either link-variant or trigger+content-variant) + four panel layouts (`NavPanelMinimal`, `NavPanelStructured`, `NavPanelGrouped`, `NavPanelEditorial`). Vendored over `@radix-ui/react-navigation-menu` — preserves the full ARIA contract (`role="menubar"` on the list, `aria-expanded` on triggers, focus-trap on open, Escape closes). `data-indicator` reflects the active-trigger visual ramp (`underline` default, `bracket` and `arrow` exported). Motion: 160ms fade + 6px translate on panel open/close; the active-rule deploy is 320ms `--ease-deploy`; both collapse under `prefers-reduced-motion: reduce`. Marketing-nav consumption contract — see [`web.md § Primary nav contract`](web.md). Archive: `2026-05-28-redesign-primary-nav` (D-RPN-1, D-RPN-7, D-RPN-11).
- **Code surfaces** — `DiffView` (a studio-themed diff viewer; mode-aware; radius 0) and `InlineShiki` (a client-only Shiki rendering primitive; lazy-loaded). Both consume the code-surface token set ([`design-tokens.md § Code surface tokens`](design-tokens.md)).
- **`NotFoundPage`** — `status: experimental`, `lives_in: packages/ui` (`packages/ui/src/marketing/not-found-page.tsx`). Anatomy: chrome wrapper + variant region (one of `editorial` · `spec` · `blueprint`) + optional `VariantRail` client island. Variants: **`editorial`** (default — italic-recession headline, single `TacticalFrame`, blueprint rule, "from the studio" mono caption), **`spec`** (mono-heavy missing-spec artifact — breadcrumb, six-row metadata table, blueprint-bordered explainer), **`blueprint`** (drafting-grid stage with axis labels, leader-line annotations, centered void frame stamped `unbuilt`). A11y baseline: page-title focus order ends on the CTA group; status-pill carries an `aria-label`; route-echo is decorative (`aria-hidden`). Token map: `canvas` / `ink` / `ink-muted` / `accent` / `blueprint` + `space-*` + `--duration-scalar`. Variant rail hidden in production per [`web.md § D-DS-11`](web.md). Marketing 404 consumption contract — see [`web.md § Marketing 404 contract`](web.md). Archive: `2026-05-26-add-marketing-not-found-page` (RAP-1239).

## 9. Iconography & glyphs

- **Iconography (R7).** The icon mapping is a single closed reference — one file owns the icon-name mapping; other spec files reference it, they never duplicate it. The icon library is `lucide-react`.
- **Glyph governance.** The studio's bracketed-letter glyph system (`PersonaBadge` `[A]`, the `StudioLogo variant="glyph"` tile) uses the `--glyph-*` token group ([`design-tokens.md § Glyph tokens`](design-tokens.md)). Glyphs are mono-weight (`--glyph-mono-weight`), sized by the `--glyph-size-{sm,md,lg}` ramp. The 56-pixel ModeRail letter-glyph application is retired (RAP-197); letter-glyphs survive only as Tier-3 chip identifiers and the logo glyph tile. The `--glyph-size-lg` slot is kept for any future Tier-3 chip primitive.

## 10. Promotion criteria (R10)

A component is promoted from a consumer into `packages/ui` when **all four** conditions hold:

1. **≥2 consumers** — at least two app surfaces (or two regions within one app) actively render it. A single consumer keeps the component co-located with that consumer.
2. **No surface-specific logic** — the component does not branch on `apps/studio` vs `apps/web` internally. Surface-specific behaviour belongs in a wrapper at the consumer site.
3. **Tokens via CSS vars** — every colour, spacing, typography, and motion value comes from `var(--…)`. No raw hex, no inline px (bar the documented exceptions in [`design-tokens.md`](design-tokens.md)).
4. **A11y contract stable** — the component's keyboard, screen-reader, focus-ring, and touch-target story is written down (in this file's entry) and tested.

These are criteria, not taste. **Migration history convention:** when a component moves into `packages/ui`, the change folder is named `promote-<componentname>`; the applied-delta paragraph records the date and source change. The `path:line` operationalisation of these criteria — where each component currently lives and its migration history — is in [`ui.md § Promotion criteria`](ui.md).

## 11. Component glossary

Status of every catalogued component.

| Component | `status` | `lives_in` | Note |
|---|---|---|---|
| `Button`, `Input`, `Textarea`, `Label`, `Select`, `Form`, `Avatar`, `Badge`, `Separator`, `Skeleton` | canonical | `packages/ui` | Form / content primitives |
| `Dialog`, `AlertDialog`, `Sheet`, `DropdownMenu`, `Tooltip`, `Collapsible`, `Popover` | canonical | `packages/ui` | Overlay primitives |
| `Tabs`, `Card`, `Sonner` | canonical | `packages/ui` | — |
| `Container`, `Stack`, `Grid`, `Spacer` | canonical | `packages/ui` | Layout primitives (RAP-211) |
| `TacticalFrame`, `DividerTechnical` | canonical | `packages/ui` | Tactical primitives |
| `Chip`, `StatusPill`, `MonoLabel`, `TechPill`, `SmartPill`, `PersonaBadge`, `ToolStatusPill` | canonical | `packages/ui` | Chip family (RAP-223) |
| `Sidebar`, `KpiTile`, `DataTable`, `SectionHead`, `MetaBar`, `StudioLogo` | canonical | `packages/ui` | Studio / workspace primitives |
| `PhasePipeline`, `LiveLogStream`, `FindingRow` | canonical | `packages/ui` | Forge primitives |
| `MediaBlock`, `DocumentShell`, `chat`, `SpecNavigator`, `NavigationMenu`, `DiffView`, `InlineShiki` | canonical | `packages/ui` | Compound primitives |
| `brand-mark.tsx` (`BrandMark`) | deprecated | `packages/ui` | Superseded by `StudioLogo` (RAP-197 / the evolve-brand-mark change) |
| `ModeRail`, `AppShell` | deprecated | — | Removed in RAP-197 (`consolidate-studio-sidebar-with-brand`) |
| `EntityCard` | deferred | `packages/ui` | Four variants `inline` / `row` / `card` / `detail`; deferred per RAP-92 |
| `SkillChip` | deferred | `packages/ui` | Lands in `packages/ui` once shipped; deferred per RAP-94 |
| `arkanoid-icon`, `period-list-item`, `timeline-strip` | deprecated | `packages/legacy-brand` | Personal-brand legacy; relocated in RAP-210, no current-brand consumer |
| `_slot.tsx` | experimental | `packages/ui` | Placeholder stub, not consumed; cleanup candidate |

## 12. Background patterns

> Decorative drafting-graphic textures for text-free zones. The
> ratified narrow exception (`D-DS-8`) to the "no gradients" hard
> constraint. Five tiled CSS utilities, three SVG composition
> components, one density budget, one usage idiom.

### Scope

Patterns are decorative paint on text-free decorative zones —
never behind text, never under interactive content, never sharing an
element with content. Three constraints stay inviolate under `D-DS-8`:

- No gradient or pattern behind text. Patterns never share an element
  with text content.
- Backgrounds behind content stay flat warm ivory (light) or near-black
  (dark).
- No colour-wash gradients, no soft shadows, no blurs. The
  `backdrop-blur` carve-out for the sticky header is separately scoped.

### Catalog

Eight patterns. Two families.

| Code | Class / component | Family | Spec | Fade-compat |
|---|---|---|---|---|
| GRID/01 | `.bg-pattern-dots` | tile · gradient | 24px · radial | yes — all four |
| GRID/02 | `.bg-pattern-graph` | tile · gradient | 8px minor + 40px major · two weights | yes — all four |
| GRID/03 | `.bg-pattern-iso` | tile · gradient | 28px · 3 axes 30°/90°/150° | yes — all four |
| HATCH/07 | `.bg-pattern-hatch` | tile · gradient | 14px · 45° (GOST 2.306) | yes — all four |
| CAD/08 | `.bg-pattern-cad` | tile · SVG mask | 60px · crosshair nodes | **no — owns `mask-image`** |
| FRAME/04 | `<FramePattern>` | composition · SVG | 1200×620 · slice · L-brackets + ticks + sheet labels | yes — all four |
| PHI/05 | `<PhiPattern>` | composition · SVG | 1200×620 · slice · nested Φ-squares + spiral | yes — all four |
| MODULOR/06 | `<ModulorPattern>` | composition · SVG | 1200×620 · stretch · 7 Φ-series bands + baseline | yes — all four |

Tiled patterns are `@utility` declarations in
`packages/ui/src/styles/patterns.css`. Composition patterns are React
server components in `packages/ui/src/components/patterns/`.

### API — composition components

The three composition components share a single CVA variant system at
`packages/ui/src/components/patterns/shared.ts`:

```ts
export type PatternProps = {
  tone?:      'blueprint' | 'ink' | 'accent' | 'current'      // default: blueprint
  intensity?: 'subtle'    | 'normal' | 'bold'                 // default: normal
  fade?:      'none'      | 'center' | 'top' | 'bottom' | 'edges'  // default: none
  className?: string
}
```

Tiled patterns inherit the same vocabulary through Tailwind utilities
on the host element: `text-blueprint`, `opacity-70`, `pattern-fade-top`.

#### Three-layer colour reading (compositions only)

`tone` does not paint the entire pattern. Composition patterns have
three independent colour layers, of which only the first reacts to
`tone`:

| Layer | Carries | Painted with | Reacts to `tone`? |
|---|---|---|---|
| 1 — structural | geometry: ticks, bands, rectangles | `currentColor` via `tone` | **yes** |
| 2 — accent overlay | L-brackets (Frame), spiral (Phi), baseline (Modulor) | `var(--color-accent)` | no — always terracotta |
| 3 — labels | `SHEET 01`, `Φ = 1.618`, `M1 · 384u` | `var(--color-ink-muted)` | no |

`tone="accent"` shifts layer 1 to terracotta, which then merges visually
with the (already-terracotta) layer 2 — the composition reads as
single-tone accent. The label layer stays ink-muted regardless.

`tone="current"` is the escape hatch — applies no `text-*` utility,
letting the host's inherited `color` drive layer 1.

#### Intensity

Three discrete opacity buckets, not a slider:

| value | css | when |
|---|---|---|
| `subtle` | `opacity-40` | Composition default. Reads as paper texture, not figure. P3 production. |
| `normal` | `opacity-70` — **CVA default** | Tile default. P4 production reads at this weight through fade-top. |
| `bold` | `opacity-100` | Reserved. Untested in production. Risk: figure-vs-ground inversion. |

#### Fade × tile compatibility

`pattern-fade-{center,top,bottom,edges}` are mask-based and compose with
all four gradient tiles + all three composition components.
`bg-pattern-cad` is the exception — it owns `mask-image` for its
crosshair geometry, and any `pattern-fade-*` mask silently replaces it.
To soften CAD, drop opacity.

To **fade** CAD (not just dim it), nest: keep `bg-pattern-cad` on the
tile element and put `pattern-fade-*` on a **parent wrapper**.
`mask-image` applies to an element's whole painted subtree, so the
wrapper's fade mask composites over the already-masked CAD child — the
two masks multiply, the crosshairs survive, and the strip dissolves
toward content. This is distinct from the same-element pairing in the
matrix below (which silently overrides) and is the approved way to run
CAD in the bottom-strip idiom. First shipped at `connected-stack` (P5,
RAP-1265).

```tsx
<div className="… pattern-fade-top">           {/* wrapper carries the fade mask */}
  <div className="absolute inset-0 bg-pattern-cad text-blueprint opacity-70" />
</div>
```

| tile \ fade | none | center | top | bottom | edges |
|---|---|---|---|---|---|
| dots, graph, iso, hatch | ✓ | ✓ | ✓ | ✓ | ✓ |
| **cad** ‡ | ✓ | **✗** | **✗** | **✗** | **✗** |
| frame, phi, modulor | ✓ | ✓ | ✓ | ✓ | ✓ |

‡ same-element only; fade via parent wrapper — see above.

`fade-top` is the canonical pairing for bottom-anchored strips — dense
at the strip's bottom edge, dissolving upward toward content. Both
shipping placements use it. `fade-bottom` was tried first during P3/P4
implementation and reversed during review.

#### Aspect-ratio behaviour

Composition components are authored for a canonical 1200×620 canvas
(≈1.94:1). Production strips are ~12:1. Behaviour differs:

| Component | `preserveAspectRatio` | At non-canonical aspect |
|---|---|---|
| FramePattern | `xMidYMid slice` | Crops. L-brackets and `SHEET 01` / `RPS · 2026` labels disappear. |
| PhiPattern | `xMidYMid slice` | Crops. `Φ = 1.618` / `GOLDEN RATIO` labels go first. |
| ModulorPattern | `none` | **Stretches.** Bands stay horizontal at any aspect — proportions shift, reading survives. |

Modulor is the only composition that survives a strip-shaped host.
Frame and Phi require approximately canonical 1.94:1 — i.e. full-section
decorative zones, which do not currently exist on the home page.

### Host requirements

Patterns are children, never parents. Every host must:

- be `position: relative` so the absolute fill anchors;
- be text-free in the bounds the pattern occupies (the pattern overlay
  itself can be a child of a host that contains text *elsewhere* in
  the section, but the overlay's box must not overlap text);
- carry `overflow: hidden` if pattern bleed past the host is unwanted;
- mark the pattern overlay `aria-hidden="true"` + `pointer-events: none`
  (composition components apply these via the CVA base classes; tiled-
  pattern divs must add them manually).

### The bottom-strip idiom

The single shipping usage pattern. The wrapper sits in the section's
otherwise-empty bottom padding, as a sibling of (not ancestor of) the
section's text content:

```tsx
<div
  aria-hidden
  className="pointer-events-none absolute inset-x-0 bottom-0 h-24 md:h-32"
>
  <ModulorPattern tone="blueprint" intensity="subtle" fade="top" />
</div>
```

The section element itself must be `position: relative` for the
`absolute inset-x-0 bottom-0` anchor.

For tiled patterns, the inner is just a div with the utility class —
no React component needed:

```tsx
<div
  aria-hidden
  className="pointer-events-none absolute inset-x-0 bottom-0 h-12 md:h-24
             bg-pattern-hatch text-accent pattern-fade-top"
/>
```

#### Standard strip heights

| height | tailwind | when |
|---|---|---|
| 48 / 96 px | `h-12 md:h-24` | Tile (dense by nature). P4. |
| 64 / 128 px | `h-16 md:h-32` | Padding-match tile inside `py-16 md:py-32` sections, where the strip fills the section's bottom padding so the texture reads as a registration field beneath the content rather than a floating panel. CAD coordinate-grid idiom (see § Fade × tile compatibility); the wrapper fade-top alpha-masks the upper 35%, keeping proximity to text visually quiet. P5. |
| 96 / 128 px | `h-24 md:h-32` | Composition. Lets Modulor render ≥3 of its 7 bands. P3. |
| up to ~160 px | `h-32 md:h-40` | Only inside `py-40` sections (`for-legacy`). |

### Density budget

| Surface | Limit |
|---|---|
| Composition patterns | 1 per page |
| Tile patterns | 1 per section |
| Patterns total | 3 per page |
| Accent-tone patterns | 1 per page |
| Spacing (governing) | ≥1 bare section between any two placements |

The failure mode the budget guards is patterns clustering or competing
until they read as decoration. The true constraint is spacing, not
count: placements separated by ≥1 bare section read as punctuation.
Three well-spaced placements across the 13-section home page (06
`connected-stack`, 10 `for-legacy`, 13 `closing-cta`) clear that bar;
the total-per-page cap is 3 (raised from 2 by D-DS-9). A fourth
placement on a page this length would crowd the spacing rule and should
be rejected on that basis.

The accent-tone budget is separate and tighter — terracotta is a
precise sparse signal across the studio system (eyebrows, hero
brackets, step numerals, pillar quotes), and one accent pattern spends
that budget hard. Doubling accent dilutes both the pattern instance and
the accent system above it. (CAD/08 at `connected-stack` is blueprint,
not accent — it does not touch this sub-budget.)

### Do / Don't

| Do | Don't |
|---|---|
| Anchor patterns to text-free bottom strips. | Place patterns behind text — `D-DS-8` violation regardless of fade or opacity. |
| Use `fade-top` on bottom-anchored strips. | Use `fade-bottom` on bottom-anchored strips — reverses the read. |
| Use Modulor for horizontal strips. | Use Frame or Phi in horizontal strips — `slice` crops the labels. |
| One tile per section. | Pair two tiles in one section — the section becomes a decoration container. |
| Use accent tone at the terminal moment. | Use accent tone on a routine inter-section gap — dilutes the studio's terracotta signal. |
| `bg-pattern-cad` softened with `opacity-*`. | `bg-pattern-cad` softened with `pattern-fade-*` **on the same element** — silent mask override. |
| `bg-pattern-cad` faded via a parent wrapper carrying `pattern-fade-*` — see § Fade × tile compatibility. | — |

### Anti-patterns

The CSS will allow these. They will be caught at review and reverted.
Listed here so they're not re-proposed:

- Patterns behind text. No fade exception. No opacity exception.
- Patterns on card faces with edge-to-edge content (WorkCard,
  ServiceCard, connections tile, for-founders article — all have text
  at the inside of the card's padding, no text-free band).
- Doubled patterns on adjacent sections. The density budget is
  3/page total, not 3/page adjacent — separate by ≥1 bare section.
- `pattern-fade-*` combined with `bg-pattern-cad` **on the same element**
  (silent mask override). Fading CAD via a parent wrapper is allowed and
  documented under § Fade × tile compatibility.
- FramePattern or PhiPattern in horizontal strips (`slice` crop).
- Side-rails as thin floating elements. Dropped P1/P2 from
  `archive/2026-05-21-apply-home-background-patterns/` confirmed
  the orphaning failure mode. Reopening requires new geometry.
- Accent tone outside the narrative-anchor budget.
- `intensity="bold"` in production (untested; figure-vs-ground risk).
- Top-strips. Only bottom-strips have shipped.
- Patterns inside `<TacticalFrame>` — double framing signal.
- Conditional sections as pattern hosts (`now`, `lab`,
  `network-and-community` Layer B — they `return null` on empty data).

### Paint-maps

Per-surface paint-maps live alongside that surface's block contracts.

- **Home** — see `home/blocks.md § Background patterns paint-map`.
- **Other marketing surfaces** (`/services`, `/work`, `/network`,
  `/lab`, standalone `/contact`) — not yet drawn. Each requires its
  own structural audit before a paint-map can be designed.

### References

- `decisions.md § D-DS-8` — the gradient-exception decision (short form).
- `archive/2026-05-20-add-background-pattern-system/` — system add (RAP-1030).
- `archive/2026-05-21-ratify-pattern-gradient-exception/` — full D-DS-8 record.
- `archive/2026-05-21-apply-home-background-patterns/` — P3 + P4 application (RAP-1035).
- `packages/ui/src/styles/patterns.css` — 5 tiled `@utility` declarations + 4 fades.
- `packages/ui/src/components/patterns/` — 3 composition components + shared CVA.
- `apps/studio/src/app/(dev)/patterns/` — dev-only visual catalog.
- `design-system.md § 4 Hard constraints rule 5` — the no-gradient rule the `D-DS-8` exception narrows.
- `design-tokens.md § Hard rules at the colour level` — the inviolate clauses (2–3) the exception does not touch.

## Cross-references

- [`design-system.md`](design-system.md) — visual intent; the WCAG 2.2 AA a11y contract every component baseline conforms to.
- [`design-tokens.md`](design-tokens.md) — the token groups each component's token map points into.
- [`design-motion.md`](design-motion.md) — component specs reference motion tokens; they never declare their own durations.
- [`ui.md`](ui.md) — implementation references; `path:line` anchors and the operationalised promotion criteria.
- [`linkable-ui.md`](linkable-ui.md) — the URL-state overlay system the overlay primitives integrate with.
- [`_methodology/design-system-convention.md`](../_methodology/design-system-convention.md) — rules R3, R5, R7, R10 govern this file.
