feat(composer): expand ui-system from placeholder to full interview
ui-system skill is no longer a placeholder. Full design-system and interaction-design interviews with branching, defaults, batched question clusters, and an 'accept default / skip' protocol so users move through fast when they have taste and patient when they don't. Design system interview covers: visual posture, color (with WCAG contrast verification before writing), typography, spacing, radii, shadows, density, visual motion, components, theming, and accessibility tokens (focus ring, hit targets). Interaction design interview covers: input model, state-surface declarations per action class, motion posture and triggers, breakpoints, i18n, four state postures (empty/loading/error/offline), time formatting, keyboard policy, optimistic-update strategy with rollback and conflict resolution, gestures, focus management, notification patterns, selection, and drag-and-drop. Reference banks for "make it look like Linear/Notion/Stripe/etc." translate well-known products into starting tokens — user still confirms each value, no silent invention. Templates expanded to hold the full output: - design-system/SKILL.md grew sections for accessibility tokens, density, dark-mode mapping, contrast verification, theming variants - design-system/tokens.ts now exports color (light + dark), typography (family/size/weight/line-height/letter-spacing), spacing + density- aware padding tokens, radii, shadows, motion (durations + easings), z-index scale, breakpoints, hit-target minimums - design-system/components.md gained anatomy/state/a11y coverage for 10 primitives plus an "adding a new component" workflow and a "patterns to avoid" list (nested cards, color-only meaning, etc.) - interaction-design/SKILL.md gained sections for optimistic updates, conflict resolution, gestures, focus management, notification patterns, selection/multi-select, and drag-and-drop Hard rules: never invent on the user's behalf; <TBD> instead. Verify contrast before writing color tokens. Always declare prefers-reduced- motion as honored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,137 +1,420 @@
|
|||||||
---
|
---
|
||||||
name: ui-system
|
name: ui-system
|
||||||
description: Interview the user and produce two project-local skills — a design-system skill and an interaction-design skill — that auto-load whenever UI work is in progress. Output goes to .claude/skills/<slug>-design-system/ and .claude/skills/<slug>-interaction-design/, NOT docs/planning/. The skills load themselves into context when anyone codes UI (mock or real); a plain reference doc would not. Currently a placeholder skill that captures the minimum needed for downstream phases to proceed; planned for a fuller specification pass. Use when the composer skill reaches phase 4 or 5, or when the user asks to "set up the design system" or "specify interaction patterns."
|
description: Interview the user and produce two project-local skills — a design-system skill and an interaction-design skill — that auto-load whenever UI work is in progress. Output goes to .claude/skills/<slug>-design-system/ and .claude/skills/<slug>-interaction-design/, NOT docs/planning/. The skills load themselves into context when anyone codes UI (mock or real); a plain reference doc would not. Use when the composer skill reaches phase 4 or 5, or when the user asks to "set up the design system", "specify interaction patterns", or "redo the UI system".
|
||||||
---
|
---
|
||||||
|
|
||||||
# UI system (placeholder)
|
# UI system
|
||||||
|
|
||||||
> **Status: PLACEHOLDER.** This skill currently captures the minimum
|
You run a focused interview that produces two project-local skills:
|
||||||
> design-system and interaction-design content needed for the mocks
|
|
||||||
> phase to proceed. A fuller specification pass is planned. When the
|
|
||||||
> user returns to flesh this out, expand the interview sections below
|
|
||||||
> and the SKILL.md / sidecar templates.
|
|
||||||
|
|
||||||
## Why this is a skill, not a doc
|
|
||||||
|
|
||||||
A reference document at `docs/planning/design-system.md` gets read once
|
|
||||||
and then drifts out of context as the conversation grows. UI work that
|
|
||||||
happens later — generating a mock, writing a component, picking a color —
|
|
||||||
won't have it loaded.
|
|
||||||
|
|
||||||
A **skill** with a description that triggers on UI work is loaded
|
|
||||||
automatically every time the trigger matches. That's the whole point of
|
|
||||||
the skill mechanism. The design system and interaction design must be
|
|
||||||
skills.
|
|
||||||
|
|
||||||
## What this skill produces
|
|
||||||
|
|
||||||
Two project-local skills, plus their sidecar data files:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
.claude/skills/<slug>-design-system/
|
.claude/skills/<slug>-design-system/
|
||||||
├── SKILL.md ← the skill itself; auto-loads on UI work
|
├── SKILL.md ← canonical visual reference; auto-loads on UI work
|
||||||
├── tokens.ts ← machine-readable color / type / spacing / motion tokens
|
├── tokens.ts ← machine-readable color / type / spacing / motion tokens
|
||||||
└── components.md ← component vocabulary with anatomy
|
└── components.md ← component vocabulary with anatomy
|
||||||
|
|
||||||
.claude/skills/<slug>-interaction-design/
|
.claude/skills/<slug>-interaction-design/
|
||||||
└── SKILL.md ← interaction patterns, state surfaces, input modes, i18n
|
└── SKILL.md ← interaction patterns, state surfaces, input modes, i18n
|
||||||
```
|
```
|
||||||
|
|
||||||
And two stub pointer files in `docs/planning/` so the planning index
|
Plus pointer stubs in `docs/planning/{design-system,interaction-design}.md`
|
||||||
finds them:
|
so the planning index finds them.
|
||||||
|
|
||||||
```
|
## Why these are skills, not docs
|
||||||
docs/planning/design-system.md ← pointer; tells humans where the skill lives
|
|
||||||
docs/planning/interaction-design.md ← pointer
|
A reference doc gets read once and drifts out of context as the
|
||||||
```
|
conversation grows. UI work later — generating a mock, writing a
|
||||||
|
component, picking a color — wouldn't have it loaded.
|
||||||
|
|
||||||
|
A skill with a UI-triggered description loads automatically every time
|
||||||
|
the trigger matches. The design system and interaction design must be
|
||||||
|
skills, full stop.
|
||||||
|
|
||||||
## Slug
|
## Slug
|
||||||
|
|
||||||
The skills need a project slug for their `name:` frontmatter, so they
|
Resolve in order:
|
||||||
don't collide with skills from other projects on the same machine.
|
1. `package.json` `name` — slugify.
|
||||||
|
2. Git remote name (e.g., `git remote get-url origin` → repo name).
|
||||||
|
3. Directory name.
|
||||||
|
4. Ask.
|
||||||
|
|
||||||
Resolution order:
|
Slug rule: lowercase kebab-case, ASCII only.
|
||||||
1. If `package.json` has a `name`, slugify it.
|
`SourdoughTracker` → `sourdough-tracker` → `sourdough-tracker-design-system`.
|
||||||
2. Else if a git remote exists, slugify the repo name.
|
|
||||||
3. Else ask the user. Default to the directory name.
|
|
||||||
|
|
||||||
Slug rule: lowercase, hyphenated, kebab-case, ASCII only. Example:
|
## Interview shape
|
||||||
`SourdoughTracker` → `sourdough-tracker` → skill `sourdough-tracker-design-system`.
|
|
||||||
|
|
||||||
## design-system interview (placeholder pass)
|
Two parts, **sequential**: design system first (visual), then
|
||||||
|
interaction design (behavioral). Don't interleave — they're different
|
||||||
|
conversations and the visual posture often informs interaction choices.
|
||||||
|
|
||||||
Ask the user, in order, with sensible defaults:
|
Each phase:
|
||||||
|
1. State what's being decided and why it matters (one line).
|
||||||
|
2. Show the default if there is one.
|
||||||
|
3. Ask the question. Batch related questions when they cluster naturally.
|
||||||
|
4. Confirm the answer back as it'll appear in the artifact.
|
||||||
|
5. Move on.
|
||||||
|
|
||||||
1. **Existing material?** Brand assets, mood boards, screenshots,
|
The user can answer **`default`** or **`skip`** at any phase:
|
||||||
"make it look like Linear/Notion/Stripe", or nothing. If material
|
- `default` → accept the offered default and move on.
|
||||||
exists, ingest it before asking the rest.
|
- `skip` → mark `<TBD: ...>` and move on; user can revisit.
|
||||||
2. **Color**: brand color(s), neutral scale (warm/cool/true), dark mode
|
|
||||||
(yes/no/system). Offer to generate accessible contrast pairs.
|
|
||||||
3. **Typography**: one family or two (display + body), size scale base
|
|
||||||
and ratio, line-height policy.
|
|
||||||
4. **Spacing**: scale base (4px / 8px), key spacing tokens.
|
|
||||||
5. **Radii**: tight / medium / pillowy.
|
|
||||||
6. **Shadows**: flat / subtle / generous.
|
|
||||||
7. **Visual motion**: default duration, default easing.
|
|
||||||
8. **Components**: confirm vocabulary against `ia.md`.
|
|
||||||
|
|
||||||
Mark unanswered with `<TBD: ...>` so the return pass has a worklist.
|
Never invent on the user's behalf. `<TBD>` is a worklist, not a failure.
|
||||||
|
|
||||||
### Write phase — design-system
|
---
|
||||||
|
|
||||||
1. Write `.claude/skills/<slug>-design-system/SKILL.md` from
|
## Phase 0 — Material ingestion (silent)
|
||||||
`templates/design-system/SKILL.md`. Substitute the slug into the
|
|
||||||
`name:` field and the project name into the `description:`.
|
|
||||||
2. Write `.claude/skills/<slug>-design-system/tokens.ts` from
|
|
||||||
`templates/design-system/tokens.ts`. Fill in the values gathered in
|
|
||||||
the interview; `<TBD>` for unanswered.
|
|
||||||
3. Write `.claude/skills/<slug>-design-system/components.md` from
|
|
||||||
`templates/design-system/components.md`.
|
|
||||||
4. Write `docs/planning/design-system.md` from the pointer template —
|
|
||||||
one paragraph saying where the skill lives.
|
|
||||||
|
|
||||||
## interaction-design interview (placeholder pass)
|
Before asking anything, scan for material the user already has. Read
|
||||||
|
without asking; surface findings before the first question.
|
||||||
|
|
||||||
Ask, in order:
|
| Source | What to extract |
|
||||||
|
|---|---|
|
||||||
|
| `package.json` | name (slug), version, dependencies (UI framework hints) |
|
||||||
|
| `tailwind.config.{js,ts}` | existing tokens, theme extensions |
|
||||||
|
| `tokens.{ts,json,css}` anywhere in repo | existing token values |
|
||||||
|
| `*.css`, `*.scss` | brand colors mentioned, font families imported |
|
||||||
|
| `index.html`, `<head>` of any HTML | font loads, color-scheme meta |
|
||||||
|
| `assets/`, `public/`, `docs/` for logos/screenshots | brand colors via image inspection |
|
||||||
|
| `README.md`, `BRANDING.md`, `STYLE.md` | style notes the user wrote |
|
||||||
|
| Existing `docs/planning/concept.md` | platform hint (web/mobile/native) |
|
||||||
|
|
||||||
1. **Primary input**: touch / mouse / keyboard / mixed. Primary, supported, not supported.
|
Then before the interview opens:
|
||||||
2. **State surfaces**: pick one pattern per action class.
|
|
||||||
3. **Motion**: minimal / restrained / expressive. `prefers-reduced-motion` always honored.
|
|
||||||
4. **Breakpoints**: count, primary design target.
|
|
||||||
5. **i18n**: locales planned, RTL, string-length budget.
|
|
||||||
6. **Empty/loading/error/offline**: one declared posture each.
|
|
||||||
7. **Time formatting**: relative threshold, absolute format.
|
|
||||||
8. **Keyboard**: global shortcuts, escape behavior.
|
|
||||||
|
|
||||||
### Write phase — interaction-design
|
```
|
||||||
|
Material I found and will use:
|
||||||
|
• <thing> — <how it'll inform the interview>
|
||||||
|
• <thing>
|
||||||
|
Not found (will ask): brand color, type family, motion posture.
|
||||||
|
```
|
||||||
|
|
||||||
1. Write `.claude/skills/<slug>-interaction-design/SKILL.md` from
|
If the user has named a reference like *"make it look like Linear"* in
|
||||||
`templates/interaction-design/SKILL.md`.
|
a previous Composer phase, surface it here and apply the matching
|
||||||
2. Write `docs/planning/interaction-design.md` pointer.
|
preset (see "Reference banks" below).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Part 1 — Design system
|
||||||
|
|
||||||
|
## D1 — Visual posture (one paragraph)
|
||||||
|
|
||||||
|
The north star sentence for visual decisions. Don't ask for paragraphs;
|
||||||
|
ask for adjectives and assemble.
|
||||||
|
|
||||||
|
```
|
||||||
|
Three or four words that describe how this should feel.
|
||||||
|
Examples: "quiet, dense, monochrome, one accent" / "playful,
|
||||||
|
generous, expressive type" / "clinical, neutral, accessibility-forward".
|
||||||
|
```
|
||||||
|
|
||||||
|
Compose into a single sentence; show; accept.
|
||||||
|
|
||||||
|
## D2 — Color
|
||||||
|
|
||||||
|
Batch as a single phase with sub-questions; tight loop.
|
||||||
|
|
||||||
|
**D2.1 Brand primary.** If material gave a color, propose it. Else:
|
||||||
|
```
|
||||||
|
Pick a brand primary. Either name a hex, name a color
|
||||||
|
("warm orange", "deep teal"), or name a reference ("Linear's purple",
|
||||||
|
"Stripe's indigo"). I'll generate the variants from there.
|
||||||
|
```
|
||||||
|
|
||||||
|
**D2.2 Brand secondary.** Optional. `default` = none.
|
||||||
|
|
||||||
|
**D2.3 Neutral tone.** Warm / cool / true. One question.
|
||||||
|
```
|
||||||
|
For grays, do they feel slightly tan (warm), slightly blue (cool),
|
||||||
|
or truly neutral (true)? Default: cool.
|
||||||
|
```
|
||||||
|
|
||||||
|
**D2.4 Generate the neutral scale.** Compute 50–950 from the chosen tone
|
||||||
|
and brand contrast targets. Show the user; offer `default` or "redo with
|
||||||
|
warmer/cooler/more contrast".
|
||||||
|
|
||||||
|
**D2.5 Semantic colors.** Success / warning / error / info. Show defaults
|
||||||
|
that contrast against both light and dark backgrounds; user accepts or
|
||||||
|
overrides one at a time.
|
||||||
|
|
||||||
|
**D2.6 Dark mode.** yes / no / system-following. Default: system. If yes,
|
||||||
|
generate the dark token mapping (invert + adjust saturation per token
|
||||||
|
class) and show.
|
||||||
|
|
||||||
|
**D2.7 Accessibility check.** Compute contrast ratios between primary +
|
||||||
|
neutral pairs. Flag anything below 4.5:1 for body text or 3:1 for UI.
|
||||||
|
Don't silently accept failures — surface and confirm or adjust.
|
||||||
|
|
||||||
|
## D3 — Typography
|
||||||
|
|
||||||
|
**D3.1 One family or two?** Default: one.
|
||||||
|
|
||||||
|
**D3.2 Family choice.** If user has no preference, offer presets:
|
||||||
|
- *Modern sans*: Inter (free, broad weights)
|
||||||
|
- *Humanist*: Source Sans 3 / IBM Plex Sans
|
||||||
|
- *System stack*: native to OS (free, no load)
|
||||||
|
- *Editorial*: Inter + Fraunces (display)
|
||||||
|
- *Technical*: IBM Plex Sans + IBM Plex Mono
|
||||||
|
|
||||||
|
**D3.3 Scale base.** 14, 15, 16. Default: 16 (web), 17 (iOS native), 14 (compact dashboards).
|
||||||
|
|
||||||
|
**D3.4 Scale ratio.** 1.125 (subtle), 1.2, 1.25 (default), 1.333 (dramatic), 1.5 (display-heavy).
|
||||||
|
|
||||||
|
**D3.5 Line-height policy.**
|
||||||
|
- Display: 1.1–1.2 (tight)
|
||||||
|
- Body: 1.5 (normal)
|
||||||
|
- Long-form prose: 1.7 (relaxed)
|
||||||
|
Defaults usually fine; user can override one of three.
|
||||||
|
|
||||||
|
## D4 — Spacing
|
||||||
|
|
||||||
|
**D4.1 Base unit.** 4 (default — finer control) or 8 (rougher cadence; iOS native often).
|
||||||
|
|
||||||
|
**D4.2 Scale.** Use the standard 12-step (0, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 12, 16, 24) — almost always accepted as `default`.
|
||||||
|
|
||||||
|
## D5 — Radii
|
||||||
|
|
||||||
|
**D5.1 Posture.** sharp (0–4) / tight (4–8) / medium (8–16) / pillowy (16+) / mixed.
|
||||||
|
|
||||||
|
**D5.2 Generate sm/md/lg/pill/full.** Show; accept or tweak one.
|
||||||
|
|
||||||
|
## D6 — Shadows & elevation
|
||||||
|
|
||||||
|
**D6.1 Posture.** flat (none) / subtle / generous.
|
||||||
|
|
||||||
|
**D6.2 Scale.** xs/sm/md/lg/xl. Generated from posture; show; accept.
|
||||||
|
|
||||||
|
## D7 — Density
|
||||||
|
|
||||||
|
**D7.1 Default density.** comfortable (default; web app) / cozy / compact (data-heavy dashboard).
|
||||||
|
|
||||||
|
This drives component padding tokens — important enough to ask, easy to forget otherwise.
|
||||||
|
|
||||||
|
## D8 — Visual motion
|
||||||
|
|
||||||
|
**D8.1 Default duration.** fast (150ms), default (250ms), slow (400ms). Default: default.
|
||||||
|
**D8.2 Default easing.** standard / accel / decel / custom. Default: standard.
|
||||||
|
|
||||||
|
Behavioral motion (when things animate) lives in interaction design.
|
||||||
|
|
||||||
|
## D9 — Components
|
||||||
|
|
||||||
|
Read the entity vocabulary from `ia.md`. Confirm the standard set
|
||||||
|
(Button, Input, Card, Surface) and ask for project-specific
|
||||||
|
additions found in IA.
|
||||||
|
|
||||||
|
For each project-specific component, ask: variants? states? anatomy?
|
||||||
|
Default to "standard variants only" if user is unsure — better to add
|
||||||
|
later than over-spec now.
|
||||||
|
|
||||||
|
## D10 — Theme variants (skip by default)
|
||||||
|
|
||||||
|
Multi-brand / tenant theming usually no for v1. Default: skip.
|
||||||
|
|
||||||
|
## D11 — Accessibility tokens
|
||||||
|
|
||||||
|
**D11.1 Focus ring.** Style (solid / double / inner-outer) and color (brand / accent / outline-default). Default: solid 2px brand at 60% opacity.
|
||||||
|
|
||||||
|
**D11.2 Contrast targets.** Confirm WCAG AA (4.5:1 text, 3:1 UI). AAA optional. Default: AA.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Part 2 — Interaction design
|
||||||
|
|
||||||
|
## I1 — Input model
|
||||||
|
|
||||||
|
**I1.1 Primary input.** touch (mobile/tablet) / mouse / keyboard / mixed.
|
||||||
|
Often determined by platform from `concept.md` — confirm rather than ask cold.
|
||||||
|
|
||||||
|
**I1.2 Supported inputs.** Which secondaries must work; which are not in scope.
|
||||||
|
|
||||||
|
**I1.3 Voice / switch / assistive.** Defer unless `users.md` has an actor that needs it.
|
||||||
|
|
||||||
|
## I2 — State surface declarations
|
||||||
|
|
||||||
|
The most important phase. For each action class, pick one pattern. Walk
|
||||||
|
through them in this order:
|
||||||
|
|
||||||
|
| Action class | Common picks | Default |
|
||||||
|
|---|---|---|
|
||||||
|
| Quick edit (single field) | inline edit / popover / drawer | inline edit |
|
||||||
|
| Multi-field create | drawer / modal / full-page route | drawer (web), modal (native) |
|
||||||
|
| Multi-field edit | drawer / modal / full-page route | same as create |
|
||||||
|
| Confirm destructive | modal | modal (always — non-negotiable) |
|
||||||
|
| Settings / configuration | full-page route | full-page route |
|
||||||
|
| Top-level navigation | sidebar / tab bar / header nav | tab bar (mobile), sidebar (desktop) |
|
||||||
|
| Detail-from-list | full route / side panel / drawer | full route |
|
||||||
|
| Filtering / sorting | inline controls / popover / drawer | inline (small set), drawer (large set) |
|
||||||
|
| Search | header search / command palette / page | header search |
|
||||||
|
|
||||||
|
For each, show the default and the alternatives. User confirms or picks.
|
||||||
|
|
||||||
|
## I3 — Motion posture
|
||||||
|
|
||||||
|
**I3.1 Posture.** minimal / restrained / expressive.
|
||||||
|
|
||||||
|
**I3.2 Animation triggers.** For each, yes/no/conditional:
|
||||||
|
- Page/route transitions
|
||||||
|
- List item enter/exit
|
||||||
|
- Modal/drawer open/close
|
||||||
|
- State changes (toggle, expand)
|
||||||
|
- Loading→loaded transitions
|
||||||
|
|
||||||
|
Defaults from the posture choice — show; accept.
|
||||||
|
|
||||||
|
**I3.3 Reduced motion fallbacks.** Confirm `prefers-reduced-motion` is
|
||||||
|
honored (always yes; this is policy, not preference).
|
||||||
|
|
||||||
|
## I4 — Breakpoints
|
||||||
|
|
||||||
|
**I4.1 Count.** 2 (mobile / desktop) / 3 (mobile / tablet / desktop) / 4 (with large desktop) / fluid.
|
||||||
|
|
||||||
|
**I4.2 Values.** Defaults: 640 / 768 / 1024 / 1280. Tweakable.
|
||||||
|
|
||||||
|
**I4.3 Primary target.** mobile-first / desktop-first / fluid. Determined by platform.
|
||||||
|
|
||||||
|
## I5 — i18n
|
||||||
|
|
||||||
|
**I5.1 Locales at launch.** Default: just English (`en` or `en-US`).
|
||||||
|
|
||||||
|
**I5.2 RTL support.** yes / no / planned. Default: no in v1.
|
||||||
|
|
||||||
|
**I5.3 Pluralization.** ICU MessageFormat / library / manual. Default: ICU MessageFormat (industry standard).
|
||||||
|
|
||||||
|
**I5.4 String length budget.** Default: assume +30% over English for de/fr translations. Always declared.
|
||||||
|
|
||||||
|
## I6 — State postures
|
||||||
|
|
||||||
|
Four sub-decisions; one declared posture each.
|
||||||
|
|
||||||
|
**I6.1 Empty state.**
|
||||||
|
- Visual: icon + heading + body + CTA / heading + body / heading only.
|
||||||
|
- Tone: encouraging / neutral / matter-of-fact.
|
||||||
|
- CTA: present always / present when actionable / never.
|
||||||
|
|
||||||
|
**I6.2 Loading state.**
|
||||||
|
- Default: skeleton / spinner / blocking. Pick one default; assign exceptions.
|
||||||
|
- Threshold for showing loading at all: 200ms (default) / 300ms / immediate.
|
||||||
|
|
||||||
|
**I6.3 Error state.**
|
||||||
|
- Surface: inline (default) / toast / dialog. By severity class.
|
||||||
|
- Recovery: retry / clear path forward / report.
|
||||||
|
- Copy tone: plain language always; what happened + what to do next.
|
||||||
|
|
||||||
|
**I6.4 Offline state.**
|
||||||
|
- Behavior: full subset works / banner only / "go online to continue".
|
||||||
|
- Sync: queue + flush on reconnect / fail loudly / not in v1.
|
||||||
|
|
||||||
|
## I7 — Time formatting
|
||||||
|
|
||||||
|
**I7.1 Relative-time threshold.** Within `<7 days>` / `<30 days>` / `<24 hours>` / never. Default: 7 days.
|
||||||
|
|
||||||
|
**I7.2 Absolute format.** `MMM d, yyyy h:mm a` / ISO / locale-aware. Default: locale-aware via `Intl.DateTimeFormat`.
|
||||||
|
|
||||||
|
**I7.3 Time zones.** user-local / fixed (which?) / configurable. Default: user-local.
|
||||||
|
|
||||||
|
## I8 — Keyboard
|
||||||
|
|
||||||
|
**I8.1 Global shortcuts.** Y/N for v1. If yes, list them; if no, mark "deferred to vN".
|
||||||
|
|
||||||
|
**I8.2 Modal escape.** Always yes; confirm.
|
||||||
|
|
||||||
|
**I8.3 Focus trap in modal.** Always yes; confirm. Restore on close.
|
||||||
|
|
||||||
|
**I8.4 Form submit.** Enter submits / Cmd+Enter submits / explicit only. Default: Enter submits.
|
||||||
|
|
||||||
|
**I8.5 List navigation.** Arrow keys yes/no. Default: no in v1 unless lists are central.
|
||||||
|
|
||||||
|
## I9 — Optimistic updates & error recovery
|
||||||
|
|
||||||
|
**I9.1 Default posture.**
|
||||||
|
- Optimistic with rollback (default for most actions — feels fast)
|
||||||
|
- Pessimistic (default for destructive or money-related actions)
|
||||||
|
- Mixed (most actions optimistic; some classes pessimistic)
|
||||||
|
|
||||||
|
**I9.2 Rollback strategy.** Toast + revert / silent revert / dialog. Default: toast + revert.
|
||||||
|
|
||||||
|
**I9.3 Conflict resolution.** Last-write-wins / show conflict UI / append. Default depends on platform.
|
||||||
|
|
||||||
|
## I10 — Gestures (touch only)
|
||||||
|
|
||||||
|
Skip this phase if I1.1 didn't include touch.
|
||||||
|
|
||||||
|
**I10.1 Swipe actions on rows.** Y/N. If yes: which actions, which directions.
|
||||||
|
|
||||||
|
**I10.2 Long-press menu.** Y/N. Default: yes (iOS/Android conventional).
|
||||||
|
|
||||||
|
**I10.3 Pull-to-refresh.** Y/N for list views.
|
||||||
|
|
||||||
|
## I11 — Focus management
|
||||||
|
|
||||||
|
**I11.1 After modal close.** Restore previous focus (default — non-negotiable for a11y).
|
||||||
|
|
||||||
|
**I11.2 After route change.** Heading focus (default) / container focus / no change.
|
||||||
|
|
||||||
|
**I11.3 After form error.** Focus first error field (default).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reference banks
|
||||||
|
|
||||||
|
When the user names a reference ("like Linear", "like Notion"), apply
|
||||||
|
these presets as starting points — user still confirms each value.
|
||||||
|
|
||||||
|
| Reference | Visual posture | Color tone | Type | Density | Motion | Radii |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| **Linear** | quiet, dense, monochrome with one purple accent | cool neutral | Inter | compact | restrained, fast | tight |
|
||||||
|
| **Notion** | minimal, generous spacing, friendly | warm-leaning true neutral | Inter / system | comfortable | minimal | medium |
|
||||||
|
| **Stripe** | clean, technical, indigo accent | cool neutral | Camphor / system | comfortable | restrained | medium |
|
||||||
|
| **GitHub** | utilitarian, dense, clear hierarchy | cool true neutral | -apple-system stack | compact | minimal | tight |
|
||||||
|
| **Vercel** | sharp, monochrome, contrasty | true neutral, almost B&W | Geist / Inter | comfortable | restrained, fast | tight |
|
||||||
|
| **Apple HIG** | spacious, soft, system-native | warm-cool mix per platform | SF Pro | cozy | expressive | medium |
|
||||||
|
| **Material 3** | adaptive, expressive, dynamic color | tone-based dynamic | Roboto / Plus Jakarta | comfortable | expressive | pillowy |
|
||||||
|
|
||||||
|
For "make it look exotic / brutalist / playful / etc.", branch
|
||||||
|
cleanly: ask for two or three reference URLs the user has in mind,
|
||||||
|
and reflect them back as a posture sentence rather than picking presets.
|
||||||
|
|
||||||
|
## Write phase
|
||||||
|
|
||||||
|
After the interview ends, write all six files in one batch:
|
||||||
|
|
||||||
|
1. `.claude/skills/<slug>-design-system/SKILL.md` from `templates/design-system/SKILL.md` — substitute slug, project name, and all interview answers.
|
||||||
|
2. `.claude/skills/<slug>-design-system/tokens.ts` from `templates/design-system/tokens.ts` — fill in all values; `<TBD>` for skipped phases.
|
||||||
|
3. `.claude/skills/<slug>-design-system/components.md` from `templates/design-system/components.md` — vocabulary matched to `ia.md`.
|
||||||
|
4. `.claude/skills/<slug>-interaction-design/SKILL.md` from `templates/interaction-design/SKILL.md` — fill in all interview answers.
|
||||||
|
5. `docs/planning/design-system.md` from `templates/planning-pointers/design-system.md`.
|
||||||
|
6. `docs/planning/interaction-design.md` from `templates/planning-pointers/interaction-design.md`.
|
||||||
|
|
||||||
|
Then tell the user:
|
||||||
|
|
||||||
|
```
|
||||||
|
Wrote two project skills:
|
||||||
|
.claude/skills/<slug>-design-system/ (SKILL.md, tokens.ts, components.md)
|
||||||
|
.claude/skills/<slug>-interaction-design/ (SKILL.md)
|
||||||
|
|
||||||
|
These auto-load whenever UI work is happening. They should be available
|
||||||
|
immediately in this session; if you're starting a new session in this
|
||||||
|
repo, they'll load on the first UI-related prompt.
|
||||||
|
|
||||||
|
Items still <TBD> (return-pass worklist):
|
||||||
|
• <list any phases that were skipped or had unanswered questions>
|
||||||
|
```
|
||||||
|
|
||||||
## Re-entry
|
## Re-entry
|
||||||
|
|
||||||
When the user returns to flesh this out fully:
|
Re-running the skill on a project that already has the artifacts:
|
||||||
- Expand the interviews above.
|
|
||||||
- Expand the SKILL.md and sidecar templates.
|
|
||||||
- Both skills are version-controlled in `.claude/skills/` — re-running
|
|
||||||
this phase shows the user a diff before overwriting.
|
|
||||||
|
|
||||||
The downstream contract is: `composer-mocks` reads the **skills**, not
|
1. Read both existing SKILL.md files plus tokens.ts and components.md.
|
||||||
the planning pointer files. That contract does not change as the
|
2. List what's currently set; mark `<TBD>` items prominently.
|
||||||
templates grow.
|
3. Ask: "Update everything, just the TBDs, or pick a phase?"
|
||||||
|
4. Run only the chosen scope.
|
||||||
|
5. Always show a diff before writing.
|
||||||
|
|
||||||
## Hard rules even in placeholder mode
|
## Hard rules
|
||||||
|
|
||||||
- **Always produce both skills with sidecars.** Mocks can't proceed without them.
|
- **Always produce all six files.** Mocks can't proceed without them.
|
||||||
- **Always slug-namespace** so projects on the same machine don't collide.
|
- **Always slug-namespace** so projects on the same machine don't collide.
|
||||||
- **Mark unknowns with `<TBD: ...>`** rather than inventing — TBDs are the return-pass worklist.
|
- **`<TBD: ...>` rather than invent.** TBDs are the return-pass worklist.
|
||||||
- **Don't pretend.** If the user has nothing and no opinions, say so in the SKILL.md: "No design direction set; defaults applied. Revisit before mocks are committed."
|
- **Verify a11y contrast** before writing color tokens. Don't silently accept failures.
|
||||||
- **Tell the user what just happened.** After write, print:
|
- **Honor `prefers-reduced-motion` is policy, not opinion.** Always declare it as honored.
|
||||||
```
|
- **Confirm before overwriting** existing skill files on re-entry.
|
||||||
Wrote two project skills:
|
- **Tell the user what was written and where**, plus how to verify the skills load.
|
||||||
.claude/skills/<slug>-design-system/SKILL.md
|
|
||||||
.claude/skills/<slug>-interaction-design/SKILL.md
|
|
||||||
These auto-load whenever UI work is happening. Verify after restart
|
|
||||||
with /agents (they should appear as project-local skills).
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,94 +1,140 @@
|
|||||||
---
|
---
|
||||||
name: <SLUG>-design-system
|
name: <SLUG>-design-system
|
||||||
description: Design system for the <PROJECT NAME> project. Color tokens, typography, spacing, radii, shadows, motion durations, and component vocabulary. Load and apply whenever building UI for this project — generating mocks, writing components, picking colors, configuring Tailwind, styling native screens, building themes, designing CSS, or making any visual decision. The canonical visual reference for this project; if anything visible is being touched, this skill should be in context. Pairs with <SLUG>-interaction-design.
|
description: Design system for the <PROJECT NAME> project. Color tokens, typography, spacing, radii, shadows, density, motion, accessibility tokens, and component vocabulary. Load and apply whenever building UI for this project — generating mocks, writing components, picking colors, configuring Tailwind, styling native screens, building themes, designing CSS, or making any visual decision. The canonical visual reference for this project; if anything visible is being touched, this skill should be in context. Pairs with <SLUG>-interaction-design.
|
||||||
---
|
---
|
||||||
|
|
||||||
# <PROJECT NAME> design system
|
# <PROJECT NAME> design system
|
||||||
|
|
||||||
> **Status: <draft | reviewed | locked>**
|
> **Status: <draft | reviewed | locked>**
|
||||||
> Generated by Composer's `ui-system` phase. Re-running that phase
|
> Generated by Composer's `ui-system` phase. Re-running that phase
|
||||||
> updates this skill (with confirmation).
|
> updates this skill (with confirmation on overwrite).
|
||||||
|
|
||||||
## How to use this skill
|
## How to use this skill
|
||||||
|
|
||||||
Whenever you generate or edit UI for this project — a mock, a real
|
When generating or editing UI for this project — a mock, a real
|
||||||
component, a theme, a CSS file — read this SKILL.md and the sidecar
|
component, a theme, a CSS file — read this SKILL.md and `tokens.ts`
|
||||||
files first, then use the tokens. Do not hardcode values. Do not
|
first, then use the tokens. Never hardcode visual values. Don't
|
||||||
introduce new components without proposing them here first.
|
introduce new components without proposing them in `components.md`.
|
||||||
|
|
||||||
Sidecar files in this skill directory:
|
Sidecar files in this skill directory:
|
||||||
- `tokens.ts` — machine-readable token export. Import in mocks/code.
|
- `tokens.ts` — machine-readable token export. Import in mocks/code.
|
||||||
- `components.md` — component vocabulary with anatomy.
|
- `components.md` — component vocabulary, anatomy, states.
|
||||||
|
|
||||||
## Visual posture
|
## Visual posture
|
||||||
|
|
||||||
<!-- One or two sentences describing the feel. "Quiet, dense, monochromatic with a single brand accent." Or "Playful, generous spacing, expressive type." -->
|
<!-- One-sentence north star. -->
|
||||||
|
|
||||||
<TBD: visual posture>
|
<TBD: visual posture sentence>
|
||||||
|
|
||||||
## Color
|
## Color
|
||||||
|
|
||||||
### Brand
|
### Brand
|
||||||
- Primary: `<#hex>` — <name>
|
- **Primary**: `<#hex>` — <name>
|
||||||
- (Secondary, if any): `<#hex>` — <name>
|
- **Secondary** *(if any)*: `<#hex>` — <name>
|
||||||
|
|
||||||
### Neutrals
|
### Neutrals
|
||||||
- Tone: <warm | cool | true>
|
- **Tone**: <warm | cool | true>
|
||||||
- Scale: 50 / 100 / 200 / 300 / 400 / 500 / 600 / 700 / 800 / 900 / 950
|
- **Scale**: 50 / 100 / 200 / 300 / 400 / 500 / 600 / 700 / 800 / 900 / 950
|
||||||
|
- See `tokens.ts` for hex values.
|
||||||
|
|
||||||
### Semantic
|
### Semantic
|
||||||
| Role | Value |
|
| Role | Light | Dark *(if dark mode)* |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| Success | `<#hex>` |
|
| Success | `<#hex>` | `<#hex>` |
|
||||||
| Warning | `<#hex>` |
|
| Warning | `<#hex>` | `<#hex>` |
|
||||||
| Error | `<#hex>` |
|
| Error | `<#hex>` | `<#hex>` |
|
||||||
| Info | `<#hex>` |
|
| Info | `<#hex>` | `<#hex>` |
|
||||||
|
|
||||||
### Dark mode
|
### Dark mode
|
||||||
<TBD: yes / no / system-following + token mapping>
|
- **Posture**: <yes | no | system-following>
|
||||||
|
- **Token mapping**: see `tokens.ts` `colorDark` (if applicable)
|
||||||
|
|
||||||
|
### Accessibility contrast
|
||||||
|
- **Target**: WCAG <AA | AAA>
|
||||||
|
- **Verified pairings** (against `neutral.50` light bg, `neutral.950` dark bg):
|
||||||
|
- body text: <ratio> — ✅
|
||||||
|
- UI components: <ratio> — ✅
|
||||||
|
- <flag any below threshold>
|
||||||
|
|
||||||
## Typography
|
## Typography
|
||||||
|
|
||||||
- **Display family**: <name>, weights <list>
|
- **Display family**: <name>, weights <list>
|
||||||
- **Body family**: <name>, weights <list>
|
- **Body family**: <name | "(same as display)">, weights <list>
|
||||||
- **Scale base**: <px>, ratio <1.125 | 1.2 | 1.25 | 1.333>
|
- **Mono family** *(if used)*: <name>
|
||||||
- **Sizes**: xs / sm / base / lg / xl / 2xl / 3xl / 4xl
|
- **Scale base**: <px>, ratio <1.125 | 1.2 | 1.25 | 1.333 | 1.5>
|
||||||
- **Line-height**: tight for display, normal for body, relaxed for long-form prose
|
- **Sizes**: see `tokens.ts` `fontSize` — xs / sm / base / lg / xl / 2xl / 3xl / 4xl
|
||||||
|
- **Line-height**:
|
||||||
See `tokens.ts` for exact px values.
|
- Tight (display): <value>
|
||||||
|
- Normal (body): <value>
|
||||||
|
- Relaxed (long-form): <value>
|
||||||
|
- **Letter-spacing** *(if customized)*: <values per size class>
|
||||||
|
|
||||||
## Spacing
|
## Spacing
|
||||||
|
|
||||||
- **Base unit**: <4px | 8px>
|
- **Base unit**: <4px | 8px>
|
||||||
- **Scale (in base units)**: 0, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 12, 16, 24
|
- **Scale (in base units)**: 0, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 12, 16, 24
|
||||||
|
- See `tokens.ts` `spacing` for px values.
|
||||||
|
|
||||||
## Radii
|
## Radii
|
||||||
|
|
||||||
- `sm`, `md`, `lg`, `pill`, `full` — values in `tokens.ts`.
|
- **Posture**: <sharp | tight | medium | pillowy | mixed>
|
||||||
|
- `sm` <px> · `md` <px> · `lg` <px> · `pill` 9999px · `full` 9999px
|
||||||
|
|
||||||
## Shadows
|
## Shadows & elevation
|
||||||
|
|
||||||
- `xs / sm / md / lg / xl` — values in `tokens.ts`.
|
- **Posture**: <flat | subtle | generous>
|
||||||
- (Or: "flat — no shadows" if the posture is flat.)
|
- Scale: `xs / sm / md / lg / xl` — see `tokens.ts` `shadow`
|
||||||
|
- (Or "no shadows in this system" if posture is flat — use borders for elevation hierarchy.)
|
||||||
|
|
||||||
|
## Density
|
||||||
|
|
||||||
|
- **Default**: <comfortable | cozy | compact>
|
||||||
|
- **Padding tokens**: see `tokens.ts` `padding` — `tight / default / loose`
|
||||||
|
- **Component impact**: list components whose default density differs from the global default.
|
||||||
|
|
||||||
## Visual motion
|
## Visual motion
|
||||||
|
|
||||||
- **Default duration**: <ms>
|
- **Default duration**: <ms> (token: `motion.duration.default`)
|
||||||
- **Default easing**: <named curve or cubic-bezier>
|
- **Default easing**: <named curve> (token: `motion.easing.standard`)
|
||||||
|
- **Reduced-motion fallback**: linear, instant where appropriate
|
||||||
|
|
||||||
Behavioral motion (when things animate, what does and doesn't) lives
|
Behavioral motion (when things animate, what triggers them) is in the
|
||||||
in the interaction-design skill.
|
**interaction-design** skill, not here.
|
||||||
|
|
||||||
|
## Accessibility tokens
|
||||||
|
|
||||||
|
- **Focus ring**: <style — solid 2px brand at 60% opacity / etc.>
|
||||||
|
- **Focus ring color token**: `color.focus`
|
||||||
|
- **Focus offset**: <px>
|
||||||
|
- **Hit-target minimum**: 44×44px (mobile) / 32×32px (desktop pointer)
|
||||||
|
- **Contrast targets**: WCAG <AA | AAA> — body 4.5:1, UI 3:1
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
See `components.md` in this directory for the full vocabulary and
|
See `components.md` in this directory for the full vocabulary, anatomy,
|
||||||
anatomy. The TL;DR list:
|
and state coverage. The TL;DR list of primitives:
|
||||||
|
|
||||||
- Button (primary / secondary / ghost / destructive / link)
|
- **Button** (primary / secondary / ghost / destructive / link)
|
||||||
- Input (text / textarea / select / checkbox / radio / toggle)
|
- **Input** (text / textarea / select / checkbox / radio / toggle)
|
||||||
- Card / Surface
|
- **Card / Surface**
|
||||||
- (project-specific components from `ia.md`)
|
- **Badge / Tag**
|
||||||
|
- **Avatar**
|
||||||
|
- **Tooltip**
|
||||||
|
- **Modal / Drawer / Popover** *(behavioral patterns in interaction-design skill)*
|
||||||
|
- **Tabs**
|
||||||
|
- **Menu / Dropdown**
|
||||||
|
- **Toast / Notification**
|
||||||
|
|
||||||
|
Plus project-specific components from `ia.md`: see `components.md`.
|
||||||
|
|
||||||
|
## Theming variants
|
||||||
|
|
||||||
|
- **Multi-brand / tenant theming**: <yes | no — typically no for v1>
|
||||||
|
- **Theme-switching mechanism** *(if applicable)*: <CSS variables / data-theme attr / etc.>
|
||||||
|
|
||||||
## TBD on return
|
## TBD on return
|
||||||
|
|
||||||
- <TBD: anything the placeholder pass couldn't answer>
|
<!-- Any phases that were skipped or had unanswered questions in the
|
||||||
|
ui-system interview. This is the worklist for the next pass. -->
|
||||||
|
|
||||||
|
- <TBD: ...>
|
||||||
|
|||||||
@@ -1,52 +1,174 @@
|
|||||||
# Component vocabulary
|
# Component vocabulary
|
||||||
|
|
||||||
The named components for <PROJECT NAME>. Match names from `ia.md`
|
The named components for <PROJECT NAME>. Component names match
|
||||||
where applicable. Do not introduce new components without proposing
|
`ia.md` where applicable. **Do not introduce new components without
|
||||||
them here first.
|
proposing them here first.**
|
||||||
|
|
||||||
This file is loaded automatically by the design-system skill — agents
|
This file is a sidecar of the design-system skill — agents generating
|
||||||
generating UI should be aware of every component in this list and
|
UI should be aware of every component in this list and prefer them to
|
||||||
prefer them to ad-hoc one-offs.
|
ad-hoc one-offs. New patterns belong here, not in mock files.
|
||||||
|
|
||||||
|
## How to read this
|
||||||
|
|
||||||
|
For each component:
|
||||||
|
- **Variants** — distinct visual flavors with different semantic meaning
|
||||||
|
- **Sizes** — usually `sm / md / lg`; pick the same scale across components
|
||||||
|
- **States** — every visible state the component must handle
|
||||||
|
- **Anatomy** — the named slots / regions the component is composed of
|
||||||
|
- **Tokens used** — which design-system tokens this component reads
|
||||||
|
- **A11y notes** — load-bearing accessibility rules for this component
|
||||||
|
|
||||||
## Primitives
|
## Primitives
|
||||||
|
|
||||||
### Button
|
### Button
|
||||||
- **Variants**: primary | secondary | ghost | destructive | link
|
|
||||||
- **Sizes**: sm | md | lg
|
- **Variants**: `primary` | `secondary` | `ghost` | `destructive` | `link`
|
||||||
- **States**: default | hover | active | focus | disabled | loading
|
- **Sizes**: `sm` | `md` | `lg`
|
||||||
- **Anatomy**: optional leading icon · label · optional trailing icon
|
- **States**: default | hover | active | focus | focus-visible | disabled | loading
|
||||||
- **Motion**: <TBD>
|
- **Anatomy**: optional leading icon · label · optional trailing icon · optional loading spinner
|
||||||
|
- **Tokens used**: `color.brand.primary`, `color.semantic.error` (destructive), `color.focus`, `padding.default`, `radius.md`, `motion.duration.fast`, `fontSize.sm/base/lg`, `fontWeight.medium`
|
||||||
|
- **A11y**:
|
||||||
|
- Hit target ≥ `hitTarget.mobile` for `md+` sizes
|
||||||
|
- `aria-busy` while loading; preserve label
|
||||||
|
- `aria-disabled` rather than `disabled` if the action is permission-gated rather than form-invalid
|
||||||
|
- `link` variant uses `<a>`, not `<button>`
|
||||||
|
|
||||||
### Input
|
### Input
|
||||||
- **Variants**: text | textarea | select | checkbox | radio | toggle
|
|
||||||
- **Sizes**: sm | md | lg
|
- **Variants**: `text` | `textarea` | `select` | `checkbox` | `radio` | `toggle` | `password` | `number` | `date`
|
||||||
- **States**: default | focus | error | disabled | readonly
|
- **Sizes**: `sm` | `md` | `lg`
|
||||||
- **Anatomy**: label · field · helper text · error message
|
- **States**: default | focus | filled | disabled | readonly | error | success *(rare)*
|
||||||
- **Validation pattern**: <TBD — see interaction-design skill>
|
- **Anatomy**: label · *(optional)* description · field · *(optional)* helper text · *(error or hint)* message
|
||||||
|
- **Tokens used**: `color.neutral.*`, `color.semantic.error`, `color.focus`, `padding.default`, `radius.sm`, `fontSize.base`
|
||||||
|
- **A11y**:
|
||||||
|
- Label is **always** present (visible or `aria-label`)
|
||||||
|
- Error message is `aria-describedby`-linked to the field
|
||||||
|
- `aria-invalid="true"` while error
|
||||||
|
- Required fields marked in two ways (asterisk + `required` attribute)
|
||||||
|
- `aria-describedby` for helper text
|
||||||
|
|
||||||
### Card / Surface
|
### Card / Surface
|
||||||
- **Variants**: <flat | bordered | elevated — pick or list>
|
|
||||||
- **Anatomy**: <header? · body · footer?>
|
- **Variants**: `flat` | `bordered` | `elevated` *(per design-system shadow posture)*
|
||||||
|
- **States**: default | hover *(if interactive)* | focus *(if interactive)* | disabled
|
||||||
|
- **Anatomy**: optional header · body · optional footer · optional media region
|
||||||
|
- **Tokens used**: `color.neutral.*`, `radius.lg`, `shadow.sm` (elevated), `padding.loose`
|
||||||
|
- **A11y**:
|
||||||
|
- If clickable, the entire card is one `<button>` or `<a>` — not nested interactives
|
||||||
|
- If contains interactives, card itself is not clickable
|
||||||
|
|
||||||
|
### Badge / Tag
|
||||||
|
|
||||||
|
- **Variants**: `neutral` | `success` | `warning` | `error` | `info` | `brand`
|
||||||
|
- **Sizes**: `sm` | `md`
|
||||||
|
- **States**: default | *(removable)* with close button
|
||||||
|
- **Anatomy**: optional dot · label · optional close button
|
||||||
|
- **Tokens used**: `color.semantic.*`, `radius.pill`, `fontSize.xs`, `fontWeight.medium`
|
||||||
|
|
||||||
|
### Avatar
|
||||||
|
|
||||||
|
- **Variants**: `image` | `initials` | `placeholder`
|
||||||
|
- **Sizes**: `xs` | `sm` | `md` | `lg` | `xl`
|
||||||
|
- **States**: default | with status indicator
|
||||||
|
- **Anatomy**: image / initials · *(optional)* status dot
|
||||||
|
- **Tokens used**: `radius.full`, `color.neutral.*` (fallback bg), `fontSize.*` (initials sizing)
|
||||||
|
- **A11y**: `alt` text required for image variant; meaningful or empty `alt=""` for purely decorative
|
||||||
|
|
||||||
|
### Tooltip
|
||||||
|
|
||||||
|
- **Variants**: `default` | `error` | `info`
|
||||||
|
- **States**: hidden | shown
|
||||||
|
- **Anatomy**: trigger · floating panel · optional arrow
|
||||||
|
- **Tokens used**: `color.neutral.900` bg, `color.neutral.50` text, `radius.sm`, `shadow.md`, `motion.duration.fast`
|
||||||
|
- **A11y**:
|
||||||
|
- `role="tooltip"`, `aria-describedby` on trigger
|
||||||
|
- Show on hover AND focus (keyboard parity)
|
||||||
|
- Dismissible with `Esc`
|
||||||
|
- Never the only conveyance of critical information
|
||||||
|
|
||||||
|
### Modal / Drawer / Popover
|
||||||
|
|
||||||
|
These are **behavioral patterns** — see the interaction-design skill
|
||||||
|
for *when* to use which. The visual styling is here.
|
||||||
|
|
||||||
|
- **Modal**:
|
||||||
|
- Anatomy: backdrop · panel · optional header · body · optional footer · close button
|
||||||
|
- Tokens: `shadow.xl`, `radius.lg`, `motion.duration.default`, `zIndex.modal`
|
||||||
|
- Always traps focus; restores on close
|
||||||
|
- **Drawer**:
|
||||||
|
- Anatomy: backdrop · panel (side-anchored) · header · body · footer · close button
|
||||||
|
- Tokens: `shadow.xl`, `motion.duration.default`, `zIndex.drawer`
|
||||||
|
- **Popover**:
|
||||||
|
- Anatomy: trigger · floating panel · optional arrow
|
||||||
|
- Tokens: `shadow.md`, `radius.md`, `zIndex.popover`
|
||||||
|
|
||||||
|
### Tabs
|
||||||
|
|
||||||
|
- **Variants**: `underline` | `pills` | `enclosed`
|
||||||
|
- **States**: active | inactive | hover | focus | disabled
|
||||||
|
- **Anatomy**: tab list · tab · panel
|
||||||
|
- **A11y**:
|
||||||
|
- `role="tablist"`, `role="tab"`, `role="tabpanel"`
|
||||||
|
- Arrow-key navigation between tabs (per WAI-ARIA Authoring Practices)
|
||||||
|
- Tab `aria-selected` and `tabindex` managed appropriately
|
||||||
|
|
||||||
|
### Menu / Dropdown
|
||||||
|
|
||||||
|
- **Variants**: `default` | `with-icons` | `with-shortcuts` | `submenu`
|
||||||
|
- **States**: closed | open
|
||||||
|
- **Anatomy**: trigger · floating panel · menu items · separators
|
||||||
|
- **Tokens used**: `shadow.md`, `radius.md`, `padding.tight`, `zIndex.dropdown`
|
||||||
|
- **A11y**:
|
||||||
|
- `role="menu"`, `role="menuitem"`
|
||||||
|
- Arrow-key navigation; `Esc` closes
|
||||||
|
- Type-ahead is encouraged
|
||||||
|
- Focus returns to trigger on close
|
||||||
|
|
||||||
|
### Toast / Notification
|
||||||
|
|
||||||
|
- **Variants**: `default` | `success` | `warning` | `error` | `info`
|
||||||
|
- **Anatomy**: optional icon · title · optional description · optional action button · close button
|
||||||
|
- **Tokens used**: `color.semantic.*`, `radius.md`, `shadow.lg`, `motion.duration.default`, `zIndex.toast`
|
||||||
|
- **A11y**:
|
||||||
|
- `role="status"` for non-critical, `role="alert"` for critical
|
||||||
|
- Dismissible by user; don't auto-dismiss critical errors
|
||||||
|
- Stack region is `aria-live="polite"` (or `assertive` for errors)
|
||||||
|
|
||||||
## Project-specific components
|
## Project-specific components
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Pulled from ia.md. One block each, with anatomy and any state-specific
|
Pulled from ia.md. One block each, with anatomy and any state-specific
|
||||||
notes that should always apply when this component is rendered.
|
notes that should always apply when this component is rendered. Match
|
||||||
|
the format of the primitives above.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### <Component name>
|
### <Component name>
|
||||||
|
|
||||||
- **Purpose**: <one line>
|
- **Variants**: <list>
|
||||||
- **Anatomy**: <slots / regions>
|
- **Sizes**: <list>
|
||||||
- **States**: <list>
|
- **States**: <list>
|
||||||
- **Notes**: <anything load-bearing>
|
- **Anatomy**: <slots / regions>
|
||||||
|
- **Tokens used**: <list>
|
||||||
|
- **A11y**: <load-bearing rules>
|
||||||
|
- **Notes**: <anything else>
|
||||||
|
|
||||||
## Patterns to avoid
|
## Patterns to avoid
|
||||||
|
|
||||||
<!--
|
Things that come up often and should be vetoed by default. Saves the
|
||||||
Things that come up often and should be vetoed. Saves the agent from
|
agent from re-inventing them.
|
||||||
re-inventing them.
|
|
||||||
-->
|
|
||||||
|
|
||||||
- <TBD: e.g., "no nested cards", "no inline icons in body text", etc.>
|
- **Nested cards** — flatten or use sections; visual hierarchy comes from spacing and dividers, not stacking surfaces
|
||||||
|
- **Inline icons in body text** — icons in body text rarely scale or align well; use them in headers, buttons, badges
|
||||||
|
- **Modal-from-modal** — if a modal needs to launch another modal, the first modal is doing too much
|
||||||
|
- **Tooltip as the only conveyance** — tooltips are supplementary; the primary information must be available without hover
|
||||||
|
- **Color-only meaning** — every color-coded distinction needs a redundant signal (icon, label, position)
|
||||||
|
- **Custom focus rings that don't meet contrast** — use the system focus-ring token
|
||||||
|
- **Animation that violates `prefers-reduced-motion`** — always honor the user preference
|
||||||
|
- **Click targets below 44px on touch devices**
|
||||||
|
|
||||||
|
## Adding a new component
|
||||||
|
|
||||||
|
1. Propose it here first with the same shape as the others (variants, states, anatomy, tokens, a11y).
|
||||||
|
2. Add it to `tokens.ts` if it needs new tokens.
|
||||||
|
3. Add a mock that uses it before writing real-code implementation.
|
||||||
|
4. Council review (Maya for a11y, Marge for end-user) before locking in.
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
// Design tokens for <PROJECT NAME>.
|
// Design tokens for <PROJECT NAME>.
|
||||||
// Generated by Composer's ui-system phase.
|
// Generated by Composer's ui-system phase.
|
||||||
// Single source of truth for colors, type, spacing, radii, shadows, motion.
|
// Single source of truth — colors, type, spacing, radii, shadows,
|
||||||
// Import this in mocks and (eventually) in real UI code; do not hardcode.
|
// density, motion, accessibility. Import this in mocks AND in real
|
||||||
|
// UI code; do not hardcode visual values.
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// COLOR
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
export const color = {
|
export const color = {
|
||||||
brand: {
|
brand: {
|
||||||
primary: "<TBD: #hex>",
|
primary: "<TBD: #hex>",
|
||||||
// secondary: "<TBD>",
|
// secondary: "<TBD>",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Neutral scale, tone: <warm | cool | true>
|
||||||
neutral: {
|
neutral: {
|
||||||
50: "<TBD>",
|
50: "<TBD>",
|
||||||
100: "<TBD>",
|
100: "<TBD>",
|
||||||
@@ -21,81 +28,189 @@ export const color = {
|
|||||||
900: "<TBD>",
|
900: "<TBD>",
|
||||||
950: "<TBD>",
|
950: "<TBD>",
|
||||||
},
|
},
|
||||||
|
|
||||||
semantic: {
|
semantic: {
|
||||||
success: "<TBD>",
|
success: "<TBD>",
|
||||||
warning: "<TBD>",
|
warning: "<TBD>",
|
||||||
error: "<TBD>",
|
error: "<TBD>",
|
||||||
info: "<TBD>",
|
info: "<TBD>",
|
||||||
},
|
},
|
||||||
// dark mode tokens (if applicable):
|
|
||||||
// dark: { ... }
|
// Accessibility tokens
|
||||||
|
focus: "<TBD>", // focus-ring color
|
||||||
|
selection: "<TBD>", // text selection background
|
||||||
|
|
||||||
|
// Text on each neutral background, pre-verified for contrast
|
||||||
|
textOnLight: "<TBD>", // body text on neutral.50–neutral.200 — must be ≥ 4.5:1
|
||||||
|
textOnDark: "<TBD>", // body text on neutral.800–neutral.950 — must be ≥ 4.5:1
|
||||||
|
textMuted: "<TBD>", // secondary text — must be ≥ 4.5:1 for body, 3:1 for UI
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// Dark-mode overrides (only filled in if dark mode = yes/system).
|
||||||
|
// When dark mode is active, prefer these over `color.*`.
|
||||||
|
export const colorDark = {
|
||||||
|
brand: { primary: "<TBD>" },
|
||||||
|
neutral: { 50: "<TBD>", 100: "<TBD>", /* … */ 950: "<TBD>" },
|
||||||
|
semantic: { success: "<TBD>", warning: "<TBD>", error: "<TBD>", info: "<TBD>" },
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// TYPOGRAPHY
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
export const fontFamily = {
|
export const fontFamily = {
|
||||||
display: "<TBD: name>, system-ui, sans-serif",
|
display: '"<TBD: name>", system-ui, -apple-system, sans-serif',
|
||||||
body: "<TBD: name>, system-ui, sans-serif",
|
body: '"<TBD: name>", system-ui, -apple-system, sans-serif',
|
||||||
mono: "ui-monospace, SFMono-Regular, monospace",
|
mono: 'ui-monospace, "SF Mono", Menlo, Consolas, monospace',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// Modular scale — base × ratio
|
||||||
export const fontSize = {
|
export const fontSize = {
|
||||||
xs: "12px",
|
xs: "12px",
|
||||||
sm: "14px",
|
sm: "14px",
|
||||||
base:"16px",
|
base: "16px", // <TBD: confirm base>
|
||||||
lg: "18px",
|
lg: "18px",
|
||||||
xl: "20px",
|
xl: "20px",
|
||||||
"2xl":"24px",
|
"2xl": "24px",
|
||||||
"3xl":"30px",
|
"3xl": "30px",
|
||||||
"4xl":"36px",
|
"4xl": "36px",
|
||||||
|
"5xl": "48px",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const fontWeight = {
|
||||||
|
regular: 400,
|
||||||
|
medium: 500,
|
||||||
|
semibold: 600,
|
||||||
|
bold: 700,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const lineHeight = {
|
export const lineHeight = {
|
||||||
tight: 1.2,
|
tight: 1.2, // display
|
||||||
normal: 1.5,
|
normal: 1.5, // body
|
||||||
relaxed: 1.7,
|
relaxed: 1.7, // long-form prose
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Spacing scale in base units. Multiply by base (4 or 8) for px.
|
export const letterSpacing = {
|
||||||
export const spacingBase = 4; // <TBD: 4 or 8>
|
tight: "-0.02em",
|
||||||
export const spacing = {
|
normal: "0",
|
||||||
0: 0,
|
wide: "0.04em",
|
||||||
0.5: 0.5 * spacingBase,
|
|
||||||
1: 1 * spacingBase,
|
|
||||||
1.5: 1.5 * spacingBase,
|
|
||||||
2: 2 * spacingBase,
|
|
||||||
3: 3 * spacingBase,
|
|
||||||
4: 4 * spacingBase,
|
|
||||||
6: 6 * spacingBase,
|
|
||||||
8: 8 * spacingBase,
|
|
||||||
12: 12 * spacingBase,
|
|
||||||
16: 16 * spacingBase,
|
|
||||||
24: 24 * spacingBase,
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SPACING
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const spacingBase = 4; // <TBD: 4 or 8>
|
||||||
|
|
||||||
|
export const spacing = {
|
||||||
|
0: 0,
|
||||||
|
0.5: 0.5 * spacingBase,
|
||||||
|
1: 1 * spacingBase,
|
||||||
|
1.5: 1.5 * spacingBase,
|
||||||
|
2: 2 * spacingBase,
|
||||||
|
3: 3 * spacingBase,
|
||||||
|
4: 4 * spacingBase,
|
||||||
|
6: 6 * spacingBase,
|
||||||
|
8: 8 * spacingBase,
|
||||||
|
12: 12 * spacingBase,
|
||||||
|
16: 16 * spacingBase,
|
||||||
|
24: 24 * spacingBase,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Padding tokens by density posture — components reference these
|
||||||
|
// rather than picking spacing values directly. Switch the global
|
||||||
|
// density by re-pointing `density` (or per-component override).
|
||||||
|
export const padding = {
|
||||||
|
tight: { x: spacing[2], y: spacing[1] },
|
||||||
|
default: { x: spacing[3], y: spacing[2] },
|
||||||
|
loose: { x: spacing[4], y: spacing[3] },
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const density = "default" as keyof typeof padding; // <TBD: "tight" | "default" | "loose">
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// RADII
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
export const radius = {
|
export const radius = {
|
||||||
sm: "<TBD: px>",
|
none: "0",
|
||||||
md: "<TBD: px>",
|
sm: "<TBD>",
|
||||||
lg: "<TBD: px>",
|
md: "<TBD>",
|
||||||
|
lg: "<TBD>",
|
||||||
pill: "9999px",
|
pill: "9999px",
|
||||||
full: "9999px",
|
full: "9999px",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SHADOWS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
export const shadow = {
|
export const shadow = {
|
||||||
xs: "<TBD>",
|
none: "none",
|
||||||
sm: "<TBD>",
|
xs: "<TBD>",
|
||||||
md: "<TBD>",
|
sm: "<TBD>",
|
||||||
lg: "<TBD>",
|
md: "<TBD>",
|
||||||
xl: "<TBD>",
|
lg: "<TBD>",
|
||||||
|
xl: "<TBD>",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// MOTION
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
export const motion = {
|
export const motion = {
|
||||||
duration: {
|
duration: {
|
||||||
|
instant: 0,
|
||||||
fast: 150,
|
fast: 150,
|
||||||
default: 250,
|
default: 250,
|
||||||
slow: 400,
|
slow: 400,
|
||||||
|
slower: 600,
|
||||||
},
|
},
|
||||||
easing: {
|
easing: {
|
||||||
standard: "cubic-bezier(0.2, 0, 0, 1)",
|
standard: "cubic-bezier(0.2, 0, 0, 1)",
|
||||||
accel: "cubic-bezier(0.4, 0, 1, 1)",
|
accel: "cubic-bezier(0.4, 0, 1, 1)",
|
||||||
decel: "cubic-bezier(0, 0, 0.2, 1)",
|
decel: "cubic-bezier(0, 0, 0.2, 1)",
|
||||||
|
bounce: "cubic-bezier(0.34, 1.56, 0.64, 1)",
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Z-INDEX
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const zIndex = {
|
||||||
|
base: 0,
|
||||||
|
raised: 10,
|
||||||
|
dropdown: 100,
|
||||||
|
sticky: 200,
|
||||||
|
drawer: 300,
|
||||||
|
modal: 400,
|
||||||
|
popover: 500,
|
||||||
|
toast: 600,
|
||||||
|
tooltip: 700,
|
||||||
|
max: 9999,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// BREAKPOINTS
|
||||||
|
// =============================================================================
|
||||||
|
// Note: breakpoint values live here for visual-token coordination, but the
|
||||||
|
// authoritative *interpretation* (mobile-first, primary target, etc.) is in
|
||||||
|
// the interaction-design skill.
|
||||||
|
|
||||||
|
export const breakpoint = {
|
||||||
|
sm: "640px",
|
||||||
|
md: "768px",
|
||||||
|
lg: "1024px",
|
||||||
|
xl: "1280px",
|
||||||
|
"2xl": "1536px",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// HIT TARGETS (a11y)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const hitTarget = {
|
||||||
|
mobile: 44, // px — WCAG 2.2 minimum
|
||||||
|
desktop: 32, // px — pointer-input minimum
|
||||||
|
} as const;
|
||||||
|
|||||||
@@ -1,112 +1,230 @@
|
|||||||
---
|
---
|
||||||
name: <SLUG>-interaction-design
|
name: <SLUG>-interaction-design
|
||||||
description: Interaction design for the <PROJECT NAME> project. State surfaces (modal vs inline vs drawer vs full-page), motion philosophy, input modes (touch/mouse/keyboard), breakpoints, i18n posture, empty/loading/error/offline state postures, time formatting, and keyboard policy. Load and apply whenever building UI behavior — modals, forms, navigation, transitions, gestures, error handling, async states, focus management. Pairs with <SLUG>-design-system.
|
description: Interaction design for the <PROJECT NAME> project. State surfaces (modal vs inline vs drawer vs full-page), motion philosophy, input modes (touch/mouse/keyboard), breakpoints, i18n posture, empty/loading/error/offline state postures, time formatting, keyboard policy, optimistic-update strategy, gesture handling, and focus management. Load and apply whenever building UI behavior — modals, forms, navigation, transitions, gestures, error handling, async states, focus management, or any decision about how the interface responds. Pairs with <SLUG>-design-system.
|
||||||
---
|
---
|
||||||
|
|
||||||
# <PROJECT NAME> interaction design
|
# <PROJECT NAME> interaction design
|
||||||
|
|
||||||
> **Status: <draft | reviewed | locked>**
|
> **Status: <draft | reviewed | locked>**
|
||||||
> Generated by Composer's `ui-system` phase. Re-running that phase
|
> Generated by Composer's `ui-system` phase. Re-running that phase
|
||||||
> updates this skill (with confirmation).
|
> updates this skill (with confirmation on overwrite).
|
||||||
|
|
||||||
## How to use this skill
|
## How to use this skill
|
||||||
|
|
||||||
When building UI *behavior* — anything beyond static styling — read
|
When building UI *behavior* — anything beyond static styling — read
|
||||||
this skill first. Don't invent new state-surface patterns, motion
|
this skill first. Don't invent new state-surface patterns, motion
|
||||||
durations, or empty-state postures. They're declared once here and
|
durations, or empty-state postures. They're declared once here and
|
||||||
reused everywhere.
|
reused everywhere. For visual tokens (color, type, spacing), see the
|
||||||
|
paired `<SLUG>-design-system` skill.
|
||||||
|
|
||||||
For visual tokens (color, type, spacing), see the paired
|
If a screen needs a pattern that isn't declared here, propose it in
|
||||||
`<SLUG>-design-system` skill.
|
this skill before writing the code — not the other way around.
|
||||||
|
|
||||||
## Input modes
|
## Input modes
|
||||||
|
|
||||||
| Mode | Status |
|
| Mode | Status | Notes |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| Touch | <TBD: primary | supported | not supported> |
|
| Touch | <primary | supported | not supported> | <…> |
|
||||||
| Mouse | <TBD> |
|
| Mouse | <…> | <…> |
|
||||||
| Keyboard | <TBD> |
|
| Keyboard | supported (always — a11y baseline) | full keyboard navigation required |
|
||||||
| Voice | <TBD: usually "not supported" in v1> |
|
| Voice | <typically not supported in v1> | — |
|
||||||
|
| Switch / assistive | <…> | — |
|
||||||
|
|
||||||
|
**Primary**: <which> — design defaults assume this mode.
|
||||||
|
|
||||||
## State surfaces
|
## State surfaces
|
||||||
|
|
||||||
One pattern per action class. Used everywhere.
|
One pattern per action class. Used everywhere. If a screen needs a
|
||||||
|
deviation, document it inline as an exception with a reason.
|
||||||
|
|
||||||
| Action class | Pattern | Notes |
|
| Action class | Pattern | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Quick edit (single field) | inline edit | <TBD> |
|
| Quick edit (single field) | <inline edit | popover | drawer> | — |
|
||||||
| Multi-field create / edit | <drawer | modal — pick one> | <TBD> |
|
| Multi-field create | <drawer | modal | full-page route> | — |
|
||||||
| Confirm destructive | modal | always |
|
| Multi-field edit | <drawer | modal | full-page route> | usually same as create |
|
||||||
| Settings / configuration | full-page route | — |
|
| Confirm destructive | modal | always — non-negotiable |
|
||||||
| Top-level navigation | <sidebar | tab bar | header nav> | <TBD> |
|
| Settings / configuration | full-page route | — |
|
||||||
| Detail-from-list | <full route | side panel> | <TBD> |
|
| Top-level navigation | <sidebar | tab bar | header nav> | — |
|
||||||
|
| Detail-from-list | <full route | side panel | drawer> | — |
|
||||||
|
| Filtering / sorting | <inline controls | popover | drawer> | — |
|
||||||
|
| Search | <header search | command palette | dedicated page> | — |
|
||||||
|
|
||||||
## Motion
|
## Motion
|
||||||
|
|
||||||
- **Posture**: <minimal | restrained | expressive>
|
- **Posture**: <minimal | restrained | expressive>
|
||||||
- **Default duration / easing**: see design-system tokens
|
- **Default duration / easing**: see design-system tokens (`motion.duration.default`, `motion.easing.standard`)
|
||||||
- **Reduced motion**: respected via `prefers-reduced-motion` everywhere; fallbacks declared per pattern below
|
|
||||||
- **When things animate**:
|
### Animation triggers
|
||||||
- Page/route transitions: <yes/no, how>
|
|
||||||
- List item enter/exit: <yes/no, how>
|
| Trigger | Animated? | Duration / easing |
|
||||||
- Modal/drawer open/close: <yes/no, how>
|
|---|---|---|
|
||||||
- State changes (toggle, expand): <yes/no, how>
|
| Page / route transitions | <yes | no | conditional> | <token> |
|
||||||
|
| List item enter / exit | <…> | <token> |
|
||||||
|
| Modal / drawer open / close | <…> | <token> |
|
||||||
|
| State changes (toggle, expand) | <…> | <token> |
|
||||||
|
| Loading → loaded transitions | <…> | <token> |
|
||||||
|
| Hover / focus state changes | <…> | <token — usually `fast`> |
|
||||||
|
|
||||||
|
### Reduced motion
|
||||||
|
|
||||||
|
`prefers-reduced-motion: reduce` is **always honored**. Fallback strategy:
|
||||||
|
- Replace transitions with instant state changes.
|
||||||
|
- Replace fades with instant show/hide.
|
||||||
|
- Replace slides with crossfades or instant.
|
||||||
|
- Keep hover/focus state changes (these are essential signals, not motion).
|
||||||
|
|
||||||
## Breakpoints
|
## Breakpoints
|
||||||
|
|
||||||
| Breakpoint | Range | Primary? |
|
| Breakpoint | Range | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Phone | < <px> | <TBD> |
|
| Phone | < <px> | <…> |
|
||||||
| Tablet | <px> – <px> | <TBD> |
|
| Tablet | <px> – <px> | <…> |
|
||||||
| Desktop | ≥ <px> | <TBD> |
|
| Desktop | ≥ <px> | <…> |
|
||||||
|
| (Large desktop) | ≥ <px> | optional |
|
||||||
|
|
||||||
**Primary design target**: <which one — "mobile-first", "desktop-first", "fluid">
|
- **Primary design target**: <mobile-first | desktop-first | fluid>
|
||||||
|
- **Fluid scale ranges** *(if used)*: <…>
|
||||||
|
- See `tokens.ts` `breakpoint` for px values.
|
||||||
|
|
||||||
## i18n
|
## i18n
|
||||||
|
|
||||||
- **Locales at launch**: <list — often just English in v1>
|
- **Locales at launch**: <list>
|
||||||
- **RTL support**: <yes | no | planned>
|
- **RTL support**: <yes | no | planned for vN>
|
||||||
- **String length budget**: assume +30% over English for German/French translations
|
- **Pluralization**: <ICU MessageFormat | library | manual>
|
||||||
- **Pluralization**: <TBD: ICU MessageFormat / library / manual>
|
- **String length budget**: assume +30% over English for de/fr translations
|
||||||
|
- **Date / number formatting**: `Intl.DateTimeFormat` / `Intl.NumberFormat` (locale-aware)
|
||||||
|
- **Locale switching mechanism**: <…>
|
||||||
|
|
||||||
## State postures
|
## State postures
|
||||||
|
|
||||||
One declared posture per state, used everywhere. No inventing on a per-screen basis.
|
One declared posture per state, used everywhere. Per-screen variation
|
||||||
|
is an exception that needs justification, not a default.
|
||||||
|
|
||||||
### Empty
|
### Empty
|
||||||
- **Visual**: <icon + heading + body + optional CTA>
|
- **Visual**: <icon + heading + body + CTA | heading + body | heading only>
|
||||||
- **Tone**: <encouraging | neutral | matter-of-fact>
|
- **Tone**: <encouraging | neutral | matter-of-fact>
|
||||||
- **CTA**: <when present, what kind of action>
|
- **CTA**: <present always | present when actionable | never>
|
||||||
|
- **Copy length**: keep to 1–2 sentences; the state itself communicates
|
||||||
|
|
||||||
### Loading
|
### Loading
|
||||||
- **Default pattern**: <skeleton | spinner | blocking>
|
- **Default pattern**: <skeleton | spinner | blocking>
|
||||||
- **When to use which**: <criteria — "skeleton for list/card containers, spinner for inline buttons, blocking only for explicit user-initiated actions">
|
- **By action class**:
|
||||||
- **Threshold for showing loading at all**: <ms — usually 200–300ms; below that, just let it pop>
|
- List / card containers: <usually skeleton>
|
||||||
|
- Inline buttons: <usually spinner-in-button>
|
||||||
|
- Page-level: <skeleton or blocking>
|
||||||
|
- **Threshold for showing loading at all**: <ms — usually 200–300ms>
|
||||||
|
|
||||||
### Error
|
### Error
|
||||||
- **Surface**: <inline | toast | dialog — by error class>
|
- **Surface by class**:
|
||||||
- **Recovery**: <retry button | clear path forward | "contact support" only as last resort>
|
- Recoverable inline (validation): inline beneath the field
|
||||||
- **Copy tone**: <plain language, name what happened, name what to do next>
|
- Recoverable async (network): toast with retry
|
||||||
|
- Unrecoverable (system): dialog
|
||||||
|
- **Recovery affordances**: <retry | clear path forward | report>
|
||||||
|
- **Copy tone**: plain language, name what happened, name what to do next
|
||||||
|
- **Copy length**: 1–2 sentences max
|
||||||
|
- **Never** show stack traces, error codes without explanation, or "An error occurred" without specifics
|
||||||
|
|
||||||
### Offline
|
### Offline
|
||||||
- **Behavior**: <feature subset that works | banner only | "go online to continue">
|
- **Behavior**: <full subset works | banner only | "go online to continue">
|
||||||
- **Sync semantics**: <when reconnected, what happens>
|
- **Indicator**: <persistent banner | transient toast | none>
|
||||||
|
- **Sync semantics**: <queue + flush on reconnect | fail loudly | not in v1>
|
||||||
|
- **Conflict resolution on reconnect**: <see Optimistic updates below>
|
||||||
|
|
||||||
## Time formatting
|
## Time formatting
|
||||||
|
|
||||||
- **Relative time** for events within: <threshold — e.g., < 7 days>
|
- **Relative time** for events within: <threshold — e.g., < 7 days>
|
||||||
- **Absolute time** otherwise, format: <pattern — e.g., "MMM d, yyyy h:mm a">
|
- Pattern: "<3 hours ago>", "<yesterday>", "<last Tuesday>"
|
||||||
- **Time zones**: <user-local | fixed (which?) | configurable>
|
- **Absolute time** otherwise:
|
||||||
- **Date-only vs date+time**: <when each is shown>
|
- Pattern: <`Intl.DateTimeFormat` locale-aware | explicit format>
|
||||||
|
- Example: <e.g., "Apr 28, 2026 4:30 PM">
|
||||||
|
- **Date-only vs date+time**: show time when it matters (events, logs); date-only for ages, anniversaries, milestones
|
||||||
|
- **Time zones**: <user-local | fixed | configurable>
|
||||||
|
- **Tooltip / hover**: relative-time displays should show absolute on hover
|
||||||
|
|
||||||
## Keyboard
|
## Keyboard
|
||||||
|
|
||||||
- **Global shortcuts**: <list, or "none in v1">
|
- **Global shortcuts**: <list, or "none in v1">
|
||||||
- **Within-modal**: `Esc` closes; focus trap while open; restore focus on close
|
- **Within-modal**:
|
||||||
- **Tab order**: declared per-flow as needed; focus visible always (per design-system)
|
- `Esc` closes (always)
|
||||||
- **Forms**: `Enter` submits; `Esc` cancels destructive
|
- Focus trapped while open
|
||||||
- **Lists**: <arrow-key nav? | not in v1>
|
- Focus restored to trigger element on close
|
||||||
|
- **Tab order**: declared per-flow as relevant; focus visible always (per design-system)
|
||||||
|
- **Forms**:
|
||||||
|
- `Enter` submits *(or Cmd+Enter for multi-line forms)*
|
||||||
|
- `Esc` cancels destructive actions only
|
||||||
|
- **Lists / menus**:
|
||||||
|
- Arrow-key navigation: <yes | no in v1>
|
||||||
|
- `Home` / `End`: <yes | no>
|
||||||
|
- Type-ahead select: <yes | no>
|
||||||
|
|
||||||
|
## Optimistic updates & error recovery
|
||||||
|
|
||||||
|
- **Default posture**: <optimistic with rollback | pessimistic | mixed>
|
||||||
|
- **Optimistic by default**: most actions feel fast; rollback on failure
|
||||||
|
- **Pessimistic by default**: destructive actions, money flows, anything irreversible
|
||||||
|
- **Mixed strategy** *(if applicable)*:
|
||||||
|
- Optimistic for: <action classes>
|
||||||
|
- Pessimistic for: <action classes>
|
||||||
|
|
||||||
|
### Rollback strategy
|
||||||
|
|
||||||
|
- **UI on failure**: <toast + revert | silent revert | dialog>
|
||||||
|
- **Retry affordance**: <inline retry button | toast button | none>
|
||||||
|
- **Stale data detection**: <ETag | timestamp | version>
|
||||||
|
|
||||||
|
### Conflict resolution
|
||||||
|
|
||||||
|
- **Strategy**: <last-write-wins | show conflict UI | append both | platform-specific>
|
||||||
|
- **User-visible signaling**: <…>
|
||||||
|
|
||||||
|
## Gestures (touch only)
|
||||||
|
|
||||||
|
<!-- Skip / remove this section if input model doesn't include touch. -->
|
||||||
|
|
||||||
|
- **Swipe actions on rows**: <yes (which actions, which directions) | no>
|
||||||
|
- **Long-press menu**: <yes | no>
|
||||||
|
- **Pull-to-refresh**: <yes (where) | no>
|
||||||
|
- **Pinch-to-zoom**: <yes (where — usually images, maps) | no>
|
||||||
|
- **Edge-swipe back navigation**: <platform default | overridden>
|
||||||
|
|
||||||
|
## Focus management
|
||||||
|
|
||||||
|
- **After modal close**: restore focus to trigger element (always)
|
||||||
|
- **After route change**: <heading focus | container focus | no change>
|
||||||
|
- **After form error**: focus first error field (always)
|
||||||
|
- **After successful action that closes UI**: restore to a sensible parent
|
||||||
|
- **On initial page load**: <do not auto-focus | focus main heading | focus primary CTA>
|
||||||
|
- **Focus visibility**: always visible per design-system tokens; no `outline: none` without a replacement
|
||||||
|
|
||||||
|
## Notification patterns
|
||||||
|
|
||||||
|
- **Toasts**: <use cases — confirmations, async results>
|
||||||
|
- Position: <top-right | bottom-center | platform-default>
|
||||||
|
- Duration: <ms — usually 4000–6000>
|
||||||
|
- Dismissible: yes
|
||||||
|
- Stacking: <max N | replace>
|
||||||
|
- **Inline notifications / banners**: <use cases — status, warnings, persistent state>
|
||||||
|
- **Dialogs**: <use cases — confirmation, error, blocking decisions>
|
||||||
|
- **Badge counts**: <where shown, how cleared>
|
||||||
|
|
||||||
|
## Selection & multi-select
|
||||||
|
|
||||||
|
- **Single select**: <pattern>
|
||||||
|
- **Multi-select**:
|
||||||
|
- **Trigger**: <long-press (touch), checkbox always visible, shift-click range, etc.>
|
||||||
|
- **Action bar**: <sticky bottom | top | inline>
|
||||||
|
- **Bulk action limits**: <none | N items max>
|
||||||
|
- **Select-all**: <yes (where) | no>
|
||||||
|
|
||||||
|
## Drag & drop
|
||||||
|
|
||||||
|
- **Use cases**: <reordering | moving between lists | upload | none>
|
||||||
|
- **Affordance**: <handle-only | row-anywhere | platform-default>
|
||||||
|
- **Touch support**: <yes (long-press to lift) | desktop-only | none>
|
||||||
|
- **Live preview**: <yes | no>
|
||||||
|
|
||||||
## TBD on return
|
## TBD on return
|
||||||
|
|
||||||
- <TBD: anything the placeholder pass couldn't answer>
|
<!-- Anything skipped or deferred during the ui-system interview. -->
|
||||||
|
|
||||||
|
- <TBD: ...>
|
||||||
|
|||||||
Reference in New Issue
Block a user