@spark-web/design-system
Version:
--- title: Design System ---
237 lines (172 loc) • 8.82 kB
Markdown
# Layer 2 — Surface & Pattern files
## What this layer is
The surface and pattern layer sits between the root and individual component
rules. It has two responsibilities:
1. **Surface classifier** — determines _which product_ is being built before any
component is touched.
2. **Surface rules** — defines interaction behaviour that applies globally
across all components on that surface.
Together they answer: _"For this surface, what are the rules that override
component defaults?"_
## Where these files live
```
docs/patterns/
CLAUDE.md ← surface classifier (always read first)
internal-admin/
CLAUDE.md ← surface rules for internal admin
list-page.md ← feature pattern: list of records
form-page.md ← feature pattern: create / edit (coming soon)
detail-page.md ← feature pattern: record detail (coming soon)
customer-portal/ ← not yet defined
website/ ← not yet defined
```
## File 1 — The surface classifier (`docs/patterns/CLAUDE.md`)
### What it does
This is the agent's second read (after the root). Its only job is to map signals
from a PRD or feature brief to a named surface, then point the agent at the
correct surface rules file.
### What it must contain
**A signal-to-surface lookup table.** Words or phrases that appear in a PRD,
mapped to the surface they indicate:
| Signal in PRD | Surface |
| ---------------------------------------------------------------- | --------------- |
| "admin", "internal", "back office", "ops", "manage", "dashboard" | Internal admin |
| "customer", "portal", "my account", "self-service" | Customer portal |
| "website", "marketing", "landing page", "public" | Website |
| "mobile", "app", "iOS", "Android" | Mobile app |
**A pointer to the surface rules file for each defined surface.** Undefined
surfaces should be flagged explicitly so the agent stops and asks rather than
guessing:
```markdown
| Surface | Rules location |
| --------------- | -------------------------------------- |
| Internal admin | docs/patterns/internal-admin/CLAUDE.md |
| Customer portal | Not yet defined — flag to the team |
```
**A pointer to feature pattern files** for each surface:
```markdown
| Feature type | Pattern file |
| ---------------------------- | ----------------------------------------- |
| List of records with actions | docs/patterns/internal-admin/list-page.md |
| Create or edit a record | docs/patterns/internal-admin/form-page.md |
```
**The canonical reading order** — a numbered list reinforcing the full sequence
from root to component. This is intentional redundancy: the more times the agent
sees the reading order, the less likely it is to skip steps.
```markdown
1. docs/patterns/CLAUDE.md ← surface classifier (this file)
2. docs/patterns/[surface]/CLAUDE.md ← surface rules
3. docs/patterns/[surface]/[pattern].md ← feature pattern (if exists)
4. packages/[component]/CLAUDE.md ← component rules
5. packages/[component]/src/[component].stories.tsx ← usage examples
```
## File 2 — Surface rules (`docs/patterns/[surface]/CLAUDE.md`)
### What it does
Defines interaction and visual rules that apply across **all** components when
building for this surface. These rules sit above component-level CLAUDE.md
files. When a conflict exists between a surface rule and a component rule, the
surface rule wins.
### What it must contain
Rules that are **cross-component** in nature — things that would otherwise be
decided inconsistently component by component. Examples from the internal admin
surface:
**Row interaction rules**
Whether a table row is clickable, and what that implies for hover state.
Connecting row clickability to the data model (does a detail page exist?) rather
than leaving it to the agent to infer:
```markdown
### Clickable rows
- If the PRD describes a detail page, record view, or drill-down → row is
clickable
- If the PRD describes a read-only display with no destination → row is not
clickable
- If unsure, default to clickable
### Hover state
- Row is clickable → hover state always applied
- Row is not clickable → hover state never applied, no exceptions
```
**Overflow menu rules**
A decision tree for when a row gets an overflow menu vs. an inline button vs.
nothing:
```markdown
Does the row have actions? No → no overflow menu, no action column Yes → how
many actions? 1 action + row is NOT clickable → inline button or icon 1 action +
row IS clickable → overflow menu 2+ actions → always overflow menu
```
**Badge and pill rules**
When to use a status badge, and how to map status values to visual tones.
Explicit tone definitions prevent the agent from inventing tones:
```markdown
- Positive / active / approved / complete → `positive`
- Warning / approaching limit / expiring → `caution`
- Critical / rejected / failed / overdue → `critical`
- Neutral / inactive / archived / unknown → `neutral`
- Pending / in review / awaiting approval → `pending`
```
### What it must NOT contain
- Component-specific implementation rules (those live in component CLAUDE.md)
- Rules that only apply to one feature type (those live in pattern files)
- Duplication of anything that already lives in a lower layer
## File 3 — Feature pattern file (`[surface]/[pattern].md`)
### What it does
Describes how to assemble a specific type of page or feature — which components
to use, in what order, with what configuration. It is a recipe, not a reference.
The agent reads this _after_ the surface rules and _before_ component docs.
### What it must contain
- **Component list** — the exact packages to use for this feature type
- **Assembly order** — the layout structure from outermost to innermost
- **Per-component configuration** — which props to set, what to avoid
- **A validation checklist** — the agent must run this before marking the task
complete
Example structure for a list page:
```markdown
## Components required
- @spark-web/header
- @spark-web/table (Table, TableHeaderRow, TableHeaderCell, TableRow, TableCell)
- @spark-web/status-badge (if status column present)
- @spark-web/meatball (if row actions present)
- @spark-web/table (TablePagination, outside Table)
## Assembly order
1. Header
2. Table a. TableHeaderRow → TableHeaderCell (one per column) b. TableRow (one
per record) → TableCell
3. TablePagination (sibling of Table, never inside it)
## Validation checklist
- [ ] Header has a title prop
- [ ] Hover state matches row clickability (see surface rules)
- [ ] Meatball only present when 2+ row actions exist
- [ ] Status cells use StatusBadge — no inline styling
- [ ] TablePagination is outside Table
```
## How this layer connects to the others
| Layer | Reads from | Overrides |
| ------------------- | ------------------------------- | ---------------------------------------- |
| Root CLAUDE.md | Nothing — entry point | — |
| Surface classifier | Root (via reading order) | — |
| Surface rules | Surface classifier | Component defaults |
| Feature pattern | Surface rules | Nothing — it assembles, doesn't override |
| Component CLAUDE.md | Feature pattern (per component) | Nothing for this surface |
## How to add a new surface
1. Create a folder at `docs/patterns/[surface-name]/`.
2. Create `CLAUDE.md` inside it with the surface rules (row behaviour, badge
rules, any globally applicable interaction patterns).
3. Add a row to the signal table in `docs/patterns/CLAUDE.md` pointing to the
new file.
4. Update the root `CLAUDE.md` if needed to note the new surface.
5. Create pattern files (list, form, detail, etc.) as features are designed.
Do not build for a surface until its rules file exists. An undefined surface
means the agent has no contract and will make assumptions.
## What happens if this layer is missing or incomplete
- The agent applies component defaults regardless of surface context — hover
states, overflow menus, and badge tones become inconsistent across pages.
- Surface-level rules that conflict with component defaults are silently
ignored.
- Two agents working on the same surface independently will produce different
interaction patterns.