@tamsensedev/runner 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/README.md +47 -3
- package/dist/cli.js +2914 -0
- package/dist/cli.js.map +1 -0
- package/dist/recorder.cjs +396 -0
- package/dist/recorder.cjs.map +1 -0
- package/library/.claude-plugin/plugin.json +5 -0
- package/library/THIRD_PARTY_NOTICES.md +123 -0
- package/library/agents/code-architect.md +67 -0
- package/library/agents/code-explorer.md +61 -0
- package/library/agents/code-reviewer.md +73 -0
- package/library/agents/code-simplifier.md +62 -0
- package/library/agents/design-critic.md +52 -0
- package/library/agents/second-opinion.md +64 -0
- package/library/agents/security-auditor.md +86 -0
- package/library/agents/silent-failure-hunter.md +70 -0
- package/library/agents/test-coverage-analyzer.md +78 -0
- package/library/agents/test-runner.md +48 -0
- package/library/agents/ui-tester.md +49 -0
- package/library/licenses/Apache-2.0.txt +202 -0
- package/library/skills/accessibility/SKILL.md +144 -0
- package/library/skills/accessibility/references/wcag-checks.md +168 -0
- package/library/skills/addressing-review-feedback/SKILL.md +109 -0
- package/library/skills/app-screens/SKILL.md +96 -0
- package/library/skills/app-screens/references/shell-kpis-charts.md +108 -0
- package/library/skills/app-screens/references/states-density-phone.md +54 -0
- package/library/skills/app-screens/references/tables-forms-settings.md +104 -0
- package/library/skills/avoiding-generic-ai-design/SKILL.md +92 -0
- package/library/skills/avoiding-generic-ai-design/references/tells.md +112 -0
- package/library/skills/clean-commits/SKILL.md +85 -0
- package/library/skills/design-review/SKILL.md +107 -0
- package/library/skills/design-review/references/critique-passes.md +101 -0
- package/library/skills/design-review/references/screenshot-procedure.md +121 -0
- package/library/skills/differential-security-review/LICENSE +427 -0
- package/library/skills/differential-security-review/SKILL.md +105 -0
- package/library/skills/differential-security-review/references/adversarial.md +115 -0
- package/library/skills/differential-security-review/references/methodology.md +145 -0
- package/library/skills/differential-security-review/references/patterns.md +108 -0
- package/library/skills/false-positive-check/LICENSE +427 -0
- package/library/skills/false-positive-check/SKILL.md +95 -0
- package/library/skills/false-positive-check/references/bug-class-verification.md +128 -0
- package/library/skills/false-positive-check/references/deep-verification.md +113 -0
- package/library/skills/false-positive-check/references/evidence-templates.md +93 -0
- package/library/skills/false-positive-check/references/false-positive-patterns.md +122 -0
- package/library/skills/false-positive-check/references/gate-reviews.md +36 -0
- package/library/skills/false-positive-check/references/standard-verification.md +80 -0
- package/library/skills/focused-implementation/SKILL.md +104 -0
- package/library/skills/implementation-plan/SKILL.md +66 -0
- package/library/skills/interface-design/SKILL.md +144 -0
- package/library/skills/interface-design/references/craft-defaults.md +130 -0
- package/library/skills/interface-design/references/new-direction.md +62 -0
- package/library/skills/interface-design/references/writing.md +52 -0
- package/library/skills/interface-polish/SKILL.md +103 -0
- package/library/skills/interface-polish/references/detail-rules.md +154 -0
- package/library/skills/interface-polish/references/worst-case-data.md +158 -0
- package/library/skills/motion-design/SKILL.md +106 -0
- package/library/skills/motion-design/references/recipes.md +243 -0
- package/library/skills/postgres-performance/SKILL.md +97 -0
- package/library/skills/postgres-performance/references/rules.md +265 -0
- package/library/skills/project-orientation/SKILL.md +77 -0
- package/library/skills/reviewing-changes/SKILL.md +183 -0
- package/library/skills/safe-database-changes/SKILL.md +111 -0
- package/library/skills/safe-database-changes/references/backfills.md +75 -0
- package/library/skills/safe-database-changes/references/migration-tools.md +69 -0
- package/library/skills/safe-database-changes/references/postgres-ddl.md +199 -0
- package/library/skills/security-review/SKILL.md +132 -0
- package/library/skills/security-review/references/checks-by-area.md +107 -0
- package/library/skills/security-review/references/do-not-report.md +86 -0
- package/library/skills/spec-writing/SKILL.md +144 -0
- package/library/skills/systematic-debugging/SKILL.md +130 -0
- package/library/skills/systematic-debugging/references/flaky-tests.md +51 -0
- package/library/skills/systematic-debugging/references/root-cause-tracing.md +55 -0
- package/library/skills/task-interview/SKILL.md +84 -0
- package/library/skills/test-driven-development/SKILL.md +116 -0
- package/library/skills/test-driven-development/references/writing-good-tests.md +85 -0
- package/library/skills/ui-testing/SKILL.md +102 -0
- package/library/skills/ui-testing/references/headless-check.md +280 -0
- package/library/skills/ui-testing/references/playwright-patterns.md +134 -0
- package/library/skills/ui-testing/references/recording.md +57 -0
- package/library/skills/verification-before-completion/SKILL.md +113 -0
- package/library/skills/web-interface-guidelines/SKILL.md +202 -0
- package/package.json +46 -6
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Writing interface text
|
|
2
|
+
|
|
3
|
+
Read this when you write or change any visible text: labels, buttons, headings, empty states, errors, toasts, helper text. The product's existing voice, terminology and capitalization win; match the strings around yours.
|
|
4
|
+
|
|
5
|
+
Words are part of the design, not decoration on it. Each string does one job: helping the person understand where they are and what to do.
|
|
6
|
+
|
|
7
|
+
## Defaults
|
|
8
|
+
|
|
9
|
+
- **Sentence case** for headings, buttons, tabs, menu items, labels and table headers ("Invite members", not "Invite Members"), unless the product already uses Title Case.
|
|
10
|
+
- **Name things by what people understand**, not by how the system is built: "Notifications", not "Webhook config".
|
|
11
|
+
- **Active voice and plain verbs.** A button says exactly what happens: "Save changes", "Delete project", not "Submit", "OK" or "Continue" when a specific verb exists.
|
|
12
|
+
- **One name per action through the flow.** The button "Publish" leads to the toast "Published". Do not switch between "remove", "delete" and "archive" for the same thing.
|
|
13
|
+
- **Numerals for counts** ("8 deployments"), real plurals ("1 member", "2 members"; use the project's i18n or `Intl.PluralRules`), and units with a non-breaking space ("10 MB").
|
|
14
|
+
- **Second person**, no first person from the product ("Your projects", not "My projects" or "We could not").
|
|
15
|
+
- **Short.** Cut words that repeat the heading. If the title already explains the state, the line under it adds new information or goes.
|
|
16
|
+
|
|
17
|
+
## By situation
|
|
18
|
+
|
|
19
|
+
| Situation | Write | Avoid |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Button | Verb plus object: "Create invoice" | "Submit", "Click here", "Get started" when a precise verb exists |
|
|
22
|
+
| Destructive confirmation | Name the object and consequence: "Delete 'Q3 launch'? Its 12 tasks are deleted too." Buttons: "Delete project" / "Cancel" | "Are you sure?" with "Yes" / "No" |
|
|
23
|
+
| Error | What failed, why if known, how to recover: "Couldn't save the invoice. The connection dropped. Try again." | "Something went wrong", raw codes or stack traces, apologies, jokes |
|
|
24
|
+
| Validation | Next to the field, says what is needed: "Enter a date after the start date" | Blaming ("Invalid input"), clearing what the person typed |
|
|
25
|
+
| Empty, first use | Why it is empty and the first action: "No invoices yet. Create your first invoice to bill a client." | "Nothing here", illustrations with no action |
|
|
26
|
+
| Empty, no results | Say the filter caused it and offer to clear it | The same text as first use |
|
|
27
|
+
| Loading | Name the operation, end with an ellipsis: "Loading invoices…" | Invented progress percentages |
|
|
28
|
+
| Success | Brief, past tense: "Invoice sent" | Celebration for routine actions |
|
|
29
|
+
| Disabled control | Say why nearby or in a tooltip: "Add a payment method to upgrade" | A grey button with no reason |
|
|
30
|
+
| Helper text | Answers the question the label raises: format, limits, why it is asked | Restating the label |
|
|
31
|
+
| Placeholder | An example value, never the label | Placeholder as the only label |
|
|
32
|
+
|
|
33
|
+
## Things that read as generated
|
|
34
|
+
|
|
35
|
+
Remove these unless the product's own voice uses them:
|
|
36
|
+
|
|
37
|
+
- Genre verbs and adjectives: unlock, unleash, supercharge, elevate, empower, streamline, seamless, effortless, robust, cutting-edge, next-generation, world-class, all-in-one, blazing fast.
|
|
38
|
+
- The correction pattern: "Not just X, it's Y." Tricolon headlines ("Fast. Simple. Secure.").
|
|
39
|
+
- Performative section labels: "Field notes", "Our philosophy", "Quietly trusted by". A heading can just say "Pricing".
|
|
40
|
+
- Furniture text: version stamps, coordinates, "Scroll to explore", "Available for work" status dots, strings joined by middle dots.
|
|
41
|
+
- Long dashes as a habit; use a full stop or a comma.
|
|
42
|
+
- Captions that narrate the design ("A clean, minimal layout for…").
|
|
43
|
+
- Three items everywhere because three feels complete. Use the number the content has.
|
|
44
|
+
- Invented proof: customer counts, ratings, quotes, logos, benchmarks. Never write these unless the task supplies them.
|
|
45
|
+
|
|
46
|
+
## Micro-typography
|
|
47
|
+
|
|
48
|
+
- A real ellipsis (…) and curly quotes (“ ” ’) where the project's strings use them.
|
|
49
|
+
- No centred paragraphs over three lines; no justified text.
|
|
50
|
+
- Write complete translatable messages, not fragments glued together, so translators can reorder variables.
|
|
51
|
+
|
|
52
|
+
Sources: adapted from the writing section of the frontend-design skill in anthropics/claude-plugins-official (Apache-2.0, changed: condensed into defaults and a table), pbakaus/impeccable clarify reference (Apache-2.0, changed: rewritten, "ask before changing" steps removed), Leonxlnx/taste-skill copy reference (MIT) and vercel-labs/web-interface-guidelines (MIT).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: interface-polish
|
|
3
|
+
description: Use when finishing or refining UI so it feels right: alignment, radii, borders and shadows, icons, type details, hover, focus and press states, hit areas, overflow with long or missing data. Also for stress-testing a component with realistic worst-case data, or when a task says the UI "feels off" or needs polish.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Interface polish
|
|
7
|
+
|
|
8
|
+
Polish is a hundred small details that compound: concentric corners, icons that sit optically centred, numbers that do not jitter, a name that wraps instead of pushing the menu off the row. It is refinement inside the existing design, never a redesign in disguise. If the concept itself is wrong, say so in the report instead of quietly replacing it.
|
|
9
|
+
|
|
10
|
+
Express every fix in the project's own styling system (Tailwind in a Tailwind project, CSS modules, plain CSS, the established CSS-in-JS) and with its tokens. Never introduce a second styling system or a new dependency for a polish fix. The project's design system wins over every value below; the values are defaults where it has none.
|
|
11
|
+
|
|
12
|
+
## Process
|
|
13
|
+
|
|
14
|
+
### 1. Establish the system and scope
|
|
15
|
+
|
|
16
|
+
Read the design sources (see `interface-design` step 1) and the components on the path you are polishing. Bound the scope: the screens and components the task touches.
|
|
17
|
+
|
|
18
|
+
For each inconsistency, classify it before fixing:
|
|
19
|
+
|
|
20
|
+
- **Missing token**: the system needs a reusable value it does not have. Add it in the token layer, once.
|
|
21
|
+
- **One-off implementation**: a shared component or pattern already exists. Replace the copy with it.
|
|
22
|
+
- **Conceptual mismatch**: this flow or hierarchy differs from comparable screens. Align it with its siblings.
|
|
23
|
+
- **Local defect**: simply incomplete or inconsistent. Fix it in place.
|
|
24
|
+
|
|
25
|
+
Fix at the narrowest correct level. Do not create a shared abstraction for a single exception.
|
|
26
|
+
|
|
27
|
+
### 2. Gather evidence
|
|
28
|
+
|
|
29
|
+
Render the screens at desktop and phone widths and walk every state (procedure in `design-review`): hover, focus, active, disabled, loading, empty, error, long content. What looks slightly wrong in a screenshot is usually one of the rules below.
|
|
30
|
+
|
|
31
|
+
### 3. Triage and fix in this order
|
|
32
|
+
|
|
33
|
+
1. Broken or blocked tasks, misleading state, unreachable actions, inaccessible paths.
|
|
34
|
+
2. Missing states: loading, empty, error, success, disabled, permission.
|
|
35
|
+
3. Hierarchy, layout and responsive problems, drift from the design system.
|
|
36
|
+
4. Visual and motion inconsistencies (the rules below).
|
|
37
|
+
5. Code cleanup: dead styles, duplicated values, debug output.
|
|
38
|
+
|
|
39
|
+
Do not perfect one corner while the rest of the path stays below the same bar.
|
|
40
|
+
|
|
41
|
+
### 4. Stress-test with worst-case data
|
|
42
|
+
|
|
43
|
+
Demo data is chosen, usually without anyone noticing, to make the design look good. Feed the UI the realistic worst case: long and short names, unbreakable emails, missing optional fields, counts of 0, 1 and 1,284, translated labels, emoji, huge lists. Follow [references/worst-case-data.md](references/worst-case-data.md). Change the data at the boundary it enters (fixture, mock, API response), never by editing markup to provoke a break.
|
|
44
|
+
|
|
45
|
+
### 5. Verify
|
|
46
|
+
|
|
47
|
+
Walk the path again with mouse, keyboard and touch-size viewports. Re-take screenshots. Check the diff for accidental churn and leftover debug code.
|
|
48
|
+
|
|
49
|
+
## The rules
|
|
50
|
+
|
|
51
|
+
Code examples for each are in [references/detail-rules.md](references/detail-rules.md).
|
|
52
|
+
|
|
53
|
+
**Surfaces**
|
|
54
|
+
1. **Concentric radius.** Outer radius = inner radius + padding. Equal radii on nested surfaces are the most common reason a card "feels off". Over about 24px of padding, treat layers as separate.
|
|
55
|
+
2. **Optical alignment.** When geometric centring looks wrong, align optically: the icon side of a button gets about 2px less padding than the text side; a play triangle shifts about 2px right; fix lopsided icons in the SVG itself when you can.
|
|
56
|
+
3. **Elevation follows the product's depth strategy.** A border or a shadow, not both. A layered transparent shadow (ring plus soft depth) can replace a border on cards and buttons that sit on varied backgrounds; keep real borders for dividers, table rules and input outlines.
|
|
57
|
+
4. **Image outlines.** A 1px inset outline in pure black at 10% opacity on light, pure white at 10% on dark. Never a tinted near-black, which reads as dirt on the edge.
|
|
58
|
+
5. **Hit areas.** 44 x 44px for touch, at least 40 x 40 in dense desktop UI. Extend small controls with padding or a pseudo-element. Two hit areas never overlap.
|
|
59
|
+
|
|
60
|
+
**Type**
|
|
61
|
+
6. **Tabular figures** on numbers that update or line up: counters, prices, timers, table columns. Check the font actually has them.
|
|
62
|
+
7. **Text wrapping.** `text-wrap: balance` on headings and short centred text; `text-wrap: pretty` on paragraphs and captions; neither on long text or code.
|
|
63
|
+
8. **Font smoothing.** `-webkit-font-smoothing: antialiased` once on the root, if the project does not set it.
|
|
64
|
+
9. **Overflow.** Flex and grid children holding text get `min-width: 0`; long unbroken strings get `overflow-wrap: anywhere`; fixed-size items (avatars, icons, badges, trailing actions) get `flex-shrink: 0`.
|
|
65
|
+
|
|
66
|
+
**Icons**
|
|
67
|
+
10. **Stroke matches the text beside it**: 1.5px next to regular text, 2px next to semibold. One icon set and one stroke weight per surface.
|
|
68
|
+
11. **One SVG, recoloured by state** with `currentColor`; outline by default, filled for the active state.
|
|
69
|
+
12. **Design at render size**: use the set's native sizes (16, 20, 24), check at the smallest size used.
|
|
70
|
+
|
|
71
|
+
**Interaction**
|
|
72
|
+
13. **Every interactive element has hover, focus-visible, active and disabled states.** Hover, active and focus are more prominent than rest. Gate hover effects with `@media (hover: hover) and (pointer: fine)` when they move or scale things.
|
|
73
|
+
14. **Press feedback**: `scale(0.97)` on `:active` for buttons and pressable cards (never below 0.95), as an interruptible transition. Skip it where motion would distract (large surfaces, dense toolbars).
|
|
74
|
+
15. **Never `transition: all`.** Name the properties. Animate `transform` and `opacity` where possible. Timings in `motion-design`.
|
|
75
|
+
16. **Focus is visible** on keyboard focus (`:focus-visible`), never removed without a replacement, and not hidden under sticky headers.
|
|
76
|
+
17. **Overlays escape their container**: a dropdown inside an `overflow: hidden` parent gets clipped; use `<dialog>`, the popover API, `position: fixed` or the project's portal.
|
|
77
|
+
|
|
78
|
+
**Content**
|
|
79
|
+
18. **Truncate, wrap or clamp per field**, never globally (rules in the worst-case reference). Never truncate numbers, amounts or dates.
|
|
80
|
+
19. **Images** have explicit dimensions or `aspect-ratio`, and `object-fit: cover` where the source ratio varies.
|
|
81
|
+
|
|
82
|
+
## Common mistakes
|
|
83
|
+
|
|
84
|
+
| Mistake | Fix |
|
|
85
|
+
|---|---|
|
|
86
|
+
| Same radius on a card and the button inside it | Outer = inner + padding |
|
|
87
|
+
| Icon looks off-centre in a button | Less padding on the icon side, or fix the SVG |
|
|
88
|
+
| 1px border plus a wide soft shadow on resting cards | One or the other, per the product's depth strategy |
|
|
89
|
+
| Numbers shift as they update | `tabular-nums` |
|
|
90
|
+
| Avatar squashed into an oval in a long row | `flex-shrink: 0` on the avatar |
|
|
91
|
+
| Email pushes the row menu off-screen | `min-width: 0` on the text column, `overflow-wrap: anywhere` on the email |
|
|
92
|
+
| "1 members" | Plural rules from the project's i18n or `Intl.PluralRules` |
|
|
93
|
+
| `transition: all 300ms` | Named properties, under 300ms, ease-out |
|
|
94
|
+
| Hairline icon next to bold text | Match stroke to the text weight |
|
|
95
|
+
| Polish fix written in a second styling system | Rewrite it in the project's system |
|
|
96
|
+
|
|
97
|
+
## Output
|
|
98
|
+
|
|
99
|
+
In a code step: fix, re-check, and summarise the main fixes in the report. In a review (or when asked only to assess), return a table per category with **Severity** (high: inaccessible, misleading, unreadable or repeatedly disruptive; medium: noticeable usability or consistency problem; low: isolated polish), **Location** (`file:line` or screen and component), **Before**, **After** and **Why**. Merge a repeated issue into one row with all locations. List what you inspected per category, and label anything you did not verify as "Not verified".
|
|
100
|
+
|
|
101
|
+
Related: `interface-design`, `motion-design`, `web-interface-guidelines`, `design-review`, `accessibility`.
|
|
102
|
+
|
|
103
|
+
Sources: adapted from jakubkrehel/make-interfaces-feel-better (MIT, changed: React and Motion specifics made optional, values reconciled with other sources), emilkowalski/skills break-ui (MIT, changed: no stop-and-ask step, no shipped toggle), Dammyjay93/interface-design (MIT) and pbakaus/impeccable polish and harden references (Apache-2.0, changed: rewritten, critique storage and ask steps removed).
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Detail rules with code
|
|
2
|
+
|
|
3
|
+
Read this when applying a specific polish rule and you want the exact values. Examples are in plain CSS with a Tailwind equivalent where it helps; translate them into the project's styling system and tokens.
|
|
4
|
+
|
|
5
|
+
## Concentric radius
|
|
6
|
+
|
|
7
|
+
```css
|
|
8
|
+
.card { border-radius: 16px; padding: 8px; } /* 8 + 8 */
|
|
9
|
+
.card-inner { border-radius: 8px; }
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Tailwind: `rounded-2xl p-2` (16px radius, 8px padding) on the outer element, `rounded-lg` (8px = 16 - 8) on the inner one.
|
|
13
|
+
|
|
14
|
+
With padding over about 24px, the layers read as separate surfaces; choose each radius from the scale.
|
|
15
|
+
|
|
16
|
+
## Optical alignment
|
|
17
|
+
|
|
18
|
+
```css
|
|
19
|
+
.button-with-trailing-icon { padding-inline: 16px 14px; } /* icon side = text side - 2px */
|
|
20
|
+
.play-button svg { translate: 2px 0; } /* triangle's visual centre */
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For lopsided icons (stars, carets), adjust the SVG `viewBox` or path so no wrapper offset is needed.
|
|
24
|
+
|
|
25
|
+
## Shadow as border, and elevation
|
|
26
|
+
|
|
27
|
+
```css
|
|
28
|
+
:root {
|
|
29
|
+
--shadow-border:
|
|
30
|
+
0 0 0 1px rgb(0 0 0 / 0.06),
|
|
31
|
+
0 1px 2px -1px rgb(0 0 0 / 0.06),
|
|
32
|
+
0 2px 4px 0 rgb(0 0 0 / 0.04);
|
|
33
|
+
--shadow-border-hover:
|
|
34
|
+
0 0 0 1px rgb(0 0 0 / 0.08),
|
|
35
|
+
0 1px 2px -1px rgb(0 0 0 / 0.08),
|
|
36
|
+
0 2px 4px 0 rgb(0 0 0 / 0.06);
|
|
37
|
+
}
|
|
38
|
+
.dark {
|
|
39
|
+
--shadow-border: 0 0 0 1px rgb(255 255 255 / 0.08);
|
|
40
|
+
--shadow-border-hover: 0 0 0 1px rgb(255 255 255 / 0.13);
|
|
41
|
+
}
|
|
42
|
+
.card { box-shadow: var(--shadow-border); transition: box-shadow 150ms ease-out; }
|
|
43
|
+
.card:hover { box-shadow: var(--shadow-border-hover); }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Prefer a shadow | Keep a border |
|
|
47
|
+
|---|---|
|
|
48
|
+
| Cards and buttons on varied backgrounds | Dividers between list items |
|
|
49
|
+
| Floating layers (menus, popovers, dialogs) | Table cell boundaries |
|
|
50
|
+
| Hover lift on clickable cards | Form input outlines |
|
|
51
|
+
|
|
52
|
+
Use this only if it matches the product's depth strategy; a product built on plain borders keeps borders.
|
|
53
|
+
|
|
54
|
+
## Image outlines
|
|
55
|
+
|
|
56
|
+
```css
|
|
57
|
+
img.media { outline: 1px solid rgb(0 0 0 / 0.1); outline-offset: -1px; }
|
|
58
|
+
.dark img.media { outline-color: rgb(255 255 255 / 0.1); }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Tailwind: `outline outline-1 -outline-offset-1 outline-black/10 dark:outline-white/10`. Pure black or white only, never a tinted grey from the palette.
|
|
62
|
+
|
|
63
|
+
## Hit areas
|
|
64
|
+
|
|
65
|
+
```css
|
|
66
|
+
.icon-button { position: relative; width: 20px; height: 20px; }
|
|
67
|
+
.icon-button::after {
|
|
68
|
+
content: "";
|
|
69
|
+
position: absolute;
|
|
70
|
+
inset: 50% auto auto 50%;
|
|
71
|
+
width: 44px; height: 44px;
|
|
72
|
+
translate: -50% -50%;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If the extended area would overlap a neighbour, shrink it to the largest size that does not.
|
|
77
|
+
|
|
78
|
+
## Type rendering
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
|
82
|
+
h1, h2, h3 { text-wrap: balance; } /* short text only: browsers balance up to about 6 lines */
|
|
83
|
+
p, li, figcaption { text-wrap: pretty; }
|
|
84
|
+
.num { font-variant-numeric: tabular-nums; }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`tabular-nums` belongs on counters, timers, prices that update and table columns, not on phone numbers, version strings or decorative display numbers. Some fonts change the look of "1" with it; check it renders well.
|
|
88
|
+
|
|
89
|
+
Font family is not a polish decision: use the product's type system and never add a paid face for polish.
|
|
90
|
+
|
|
91
|
+
## Overflow in rows
|
|
92
|
+
|
|
93
|
+
```css
|
|
94
|
+
.row { display: flex; align-items: center; gap: 12px; }
|
|
95
|
+
.row-avatar { flex-shrink: 0; }
|
|
96
|
+
.row-text { min-width: 0; flex: 1; }
|
|
97
|
+
.row-email { overflow-wrap: anywhere; }
|
|
98
|
+
.row-badge { flex-shrink: 0; white-space: nowrap; }
|
|
99
|
+
.row-menu { flex-shrink: 0; }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Grid equivalent: `grid-template-columns: auto minmax(0, 1fr) auto`. When text can wrap to several lines, top-align the row (`align-items: flex-start`) so the avatar does not drift.
|
|
103
|
+
|
|
104
|
+
## Icons
|
|
105
|
+
|
|
106
|
+
| Text beside the icon | Stroke on a 24px grid |
|
|
107
|
+
|---|---|
|
|
108
|
+
| Regular (400), 14 to 16px | 1.5px |
|
|
109
|
+
| Medium or semibold (500 to 600) | 2px |
|
|
110
|
+
| Bold (700) or standalone emphasis | 2.5px |
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<svg fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">...</svg>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
.icon-button { color: var(--text-muted); }
|
|
118
|
+
.icon-button:hover { color: var(--text); }
|
|
119
|
+
.icon-button[aria-pressed="true"] { color: var(--accent); }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Strip hard-coded `fill` and `stroke` colours from imported icons. Inline icons are about 1em to 1.25em. Directional icons (back, forward, chevrons, send) flip in RTL; logos, checkmarks, clocks and media controls do not. Icon-only buttons need an accessible name; decorative icons get `aria-hidden="true"`.
|
|
123
|
+
|
|
124
|
+
## Press feedback and transitions
|
|
125
|
+
|
|
126
|
+
```css
|
|
127
|
+
.button {
|
|
128
|
+
transition-property: transform, background-color;
|
|
129
|
+
transition-duration: 150ms;
|
|
130
|
+
transition-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
|
|
131
|
+
}
|
|
132
|
+
.button:active:not(:disabled) { transform: scale(0.97); }
|
|
133
|
+
|
|
134
|
+
@media (hover: hover) and (pointer: fine) {
|
|
135
|
+
.card-link:hover { background-color: var(--surface-hover); }
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Tailwind: `transition-[transform,background-color] duration-150 active:not-disabled:scale-[0.97]`. Never `transition-all`. Use `will-change: transform` only to cure an observed first-frame stutter, never broadly.
|
|
140
|
+
|
|
141
|
+
## Focus
|
|
142
|
+
|
|
143
|
+
```css
|
|
144
|
+
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
|
|
145
|
+
.input:focus-visible { outline-offset: 0; }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Never `outline: none` without a replacement. Sticky headers and footers must not cover the focused element: `scroll-padding-top` on the scroll container equal to the header height.
|
|
149
|
+
|
|
150
|
+
## Overlays and clipping
|
|
151
|
+
|
|
152
|
+
A menu inside an `overflow: hidden` or `overflow: auto` ancestor is clipped. Use the project's popover or portal component, `<dialog>`, the `popover` attribute, or `position: fixed` with collision handling. Popovers scale from their trigger (`transform-origin` at the trigger), dialogs from the centre.
|
|
153
|
+
|
|
154
|
+
Sources: adapted from jakubkrehel/make-interfaces-feel-better surfaces, typography, icons, animations and performance references (MIT, changed: values reconciled to scale(0.97), Motion-specific code removed, plain CSS first), emilkowalski/skills break-ui failure signatures (MIT) and pbakaus/impeccable operate reference (Apache-2.0, changed: rewritten).
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Stress-testing with worst-case data
|
|
2
|
+
|
|
3
|
+
Read this when checking that a component or screen survives real data, before finishing UI work or as part of a review.
|
|
4
|
+
|
|
5
|
+
The component was almost certainly built against kind data: "Jane Doe, jane@acme.com, 12 members". Production has Aleksandra Wiśniewska-Kowalczyk, `bartholomew.fitzgerald@northwind-industries-holdings.example.com`, an intern called Jo and a workspace with 1,284 members.
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
9
|
+
1. **Plausible or schema-backed, never random.** Each value is something a real user could produce, or the actual limit from the validation schema, database column or API contract. Five thousand "a" characters prove nothing. If no limit exists, that is a finding ("unbounded"); test something long but believable.
|
|
10
|
+
2. **Change the data, not the component.** The worst case enters through the same boundary as the demo data: the fixture, mock, story args, seed, or an intercepted API response. Never edit markup or CSS to provoke a break.
|
|
11
|
+
3. **One dataset, many failures.** Spread different worst cases across the first rows (row 1 the long name, row 2 the long email, row 3 the one-letter name), the way real data mixes them.
|
|
12
|
+
4. **Do not stop at long text.** The breaks that ship are the short name, the missing avatar, "1 members", the empty list, the translated badge.
|
|
13
|
+
5. **Leave no test toggles in product code.** In a code step, a worst-case fixture may be committed only where the project keeps fixtures (tests, stories, mocks), following its conventions. In a review step, use only interception or existing fixtures and change nothing.
|
|
14
|
+
|
|
15
|
+
## Phase 1: map what the component renders
|
|
16
|
+
|
|
17
|
+
List every value it shows, with its source, type, limit and whether it is optional:
|
|
18
|
+
|
|
19
|
+
| Field | Source | Type | Limit | Optional |
|
|
20
|
+
|---|---|---|---|---|
|
|
21
|
+
| name | member.name | string | 255 (schema.ts:14) | no |
|
|
22
|
+
| email | member.email | string | none found | no |
|
|
23
|
+
| role | member.title | string | 120 | yes |
|
|
24
|
+
| status | enum | active / invited / expired | n/a | no |
|
|
25
|
+
| count | workspace.memberCount | int | n/a | no |
|
|
26
|
+
|
|
27
|
+
Include the values people forget: counts in headers, relative times, badge and status text, labels that come from data, tooltips, avatar images, and the list length itself. Find limits in validation schemas, migrations, API types and `maxlength` attributes; note where front end and back end disagree.
|
|
28
|
+
|
|
29
|
+
## Phase 2: build the worst case
|
|
30
|
+
|
|
31
|
+
Pick values from the catalog below. Also cover cases that are not one dataset:
|
|
32
|
+
|
|
33
|
+
- **Empty**: zero items, and no results after filtering.
|
|
34
|
+
- **One**: a single item, and every count at exactly 1.
|
|
35
|
+
- **Huge**: the realistic upper bound (1,000 or more rows if unpaginated).
|
|
36
|
+
|
|
37
|
+
## Phase 3: look
|
|
38
|
+
|
|
39
|
+
Check the worst case:
|
|
40
|
+
|
|
41
|
+
- at the component's real container width (a sidebar list is not a full-page list), then at 320px and the widest supported layout;
|
|
42
|
+
- with browser zoom at 200%, because text grows and boxes do not;
|
|
43
|
+
- in dark mode and with `dir="rtl"` if the product supports them.
|
|
44
|
+
|
|
45
|
+
Screenshot both demo and worst case at each width (procedure in `design-review`). Say which findings you saw and which you inferred from code.
|
|
46
|
+
|
|
47
|
+
## Catalog
|
|
48
|
+
|
|
49
|
+
Use `example.com`, `example.org` or `.test` domains so fixtures never point at real inboxes.
|
|
50
|
+
|
|
51
|
+
**People and names**
|
|
52
|
+
|
|
53
|
+
| Value | Breaks |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `Aleksandra Wiśniewska-Kowalczyk` | Long, hyphenated, diacritics; wraps at the hyphen |
|
|
56
|
+
| `Christopher Alexander Montgomery III` | Suffix; naive initials give "CI" |
|
|
57
|
+
| `Jo`, `J` | Near-empty name column; one-letter initials; tiny click target if the name is the link |
|
|
58
|
+
| `Đặng Thị Ngọc Hân` | Stacked diacritics clipped by tight line height with `overflow: hidden` |
|
|
59
|
+
| `王秀英` | No spaces; "first plus last word" initials find one word |
|
|
60
|
+
| `نور الهدى عبد الرحمن` | RTL; punctuation and icons on the wrong side without `dir="auto"` |
|
|
61
|
+
| `Seán O'Brien-Ó Súilleabháin` | Apostrophe and accents: escaping, initials, search |
|
|
62
|
+
| A name starting with an emoji ("Fox" after a fox emoji), or with a skin-tone and joiner sequence before "Priya" | `charAt(0)` returns half a character; slicing splits the sequence |
|
|
63
|
+
| ` Sam Lee ` | Leading, trailing and repeated spaces |
|
|
64
|
+
| missing name | Only an email; the UI needs a fallback |
|
|
65
|
+
|
|
66
|
+
**Emails, URLs, identifiers**
|
|
67
|
+
|
|
68
|
+
| Value | Breaks |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `bartholomew.fitzgerald@northwind-industries-holdings.example.com` | No wrap points; pushes siblings off the row |
|
|
71
|
+
| `a@b.co` | Layout that assumed a long email looks empty |
|
|
72
|
+
| `first.last+billing-notifications@example.com` | Plus addressing rejected or truncated |
|
|
73
|
+
| A long URL with query string | End truncation hides the part that differs |
|
|
74
|
+
| `9f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f` | UUID width; candidate for middle truncation |
|
|
75
|
+
| `Q3 Board Deck FINAL (revised) v12 [approved].pdf` | End truncation hides version and extension |
|
|
76
|
+
|
|
77
|
+
**Labels and text from data**
|
|
78
|
+
|
|
79
|
+
| Value | Breaks |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `Senior Product Design Engineer, Platform Infrastructure` | Wraps to three lines in a secondary slot |
|
|
82
|
+
| `Invitation expired 12 days ago` as a badge | Badge wraps or squeezes the name |
|
|
83
|
+
| `Benachrichtigungseinstellungen` | German compound, no break points |
|
|
84
|
+
| French or German UI strings | About 30% longer than English |
|
|
85
|
+
| Twelve tags on one item | Tag rows become a wall; needs "+8" |
|
|
86
|
+
| Empty string or whitespace title | Collapsed heading, zero-height row |
|
|
87
|
+
| `<script>alert(1)</script>`, `&`, `**bold**` | Must render as literal text |
|
|
88
|
+
| A 2,000-character description | Clamping, "show more", textarea growth |
|
|
89
|
+
|
|
90
|
+
**Numbers and money**
|
|
91
|
+
|
|
92
|
+
| Value | Breaks |
|
|
93
|
+
|---|---|
|
|
94
|
+
| 0 | "0 members", empty bars, division by zero in percentages |
|
|
95
|
+
| 1 | "1 members", "1 days ago" |
|
|
96
|
+
| 1284, 1000000 | Missing separators; badge width; consider compact form where precision does not matter |
|
|
97
|
+
| 12345678.9 as currency | Overflowing totals columns |
|
|
98
|
+
| -42.5 | Sign, colour logic, accounting parentheses |
|
|
99
|
+
| 0.1 + 0.2 | `0.30000000000000004` rendered raw |
|
|
100
|
+
| 142%, -3% | Progress bars past their bounds |
|
|
101
|
+
| null, undefined, NaN | Rendered literally |
|
|
102
|
+
| A live value 99 then 100 | Width jump without tabular figures |
|
|
103
|
+
|
|
104
|
+
**Collections**: 0 items; 1 item (grids that look broken with one card); exactly the page size and one more; 1,000+ unpaginated; one item ten times larger than the others; items with identical names.
|
|
105
|
+
|
|
106
|
+
**Time**: now ("0 seconds ago"); 12 days, 11 months, 3 years ago (switch to absolute dates); a future date; `1970-01-01`; a time near midnight in another time zone; a duration of 1,284 hours.
|
|
107
|
+
|
|
108
|
+
**Images**: avatar URL that fails (fallback to initials); no avatar; a 4000 x 200 panorama and a 200 x 4000 strip (need `object-fit: cover`); a dark transparent logo on a dark theme; a slow image (needs fixed dimensions).
|
|
109
|
+
|
|
110
|
+
**States**: loading; API error (no raw `TypeError` text on screen); partial data; every status value at once; no permission; the current user in the list.
|
|
111
|
+
|
|
112
|
+
## Failure signatures
|
|
113
|
+
|
|
114
|
+
| What you see | Cause | Fix |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| Avatar or icon squashed into an oval | Flex child shrinking | `flex-shrink: 0` on fixed-size items |
|
|
117
|
+
| Text overflows instead of wrapping or truncating | `min-width: auto` on a flex or grid child | `min-width: 0`, or `minmax(0, 1fr)` |
|
|
118
|
+
| Email or URL runs past the edge | No break opportunities | `overflow-wrap: anywhere` on that element |
|
|
119
|
+
| Trailing menu pushed off-screen | Middle content took the space | `min-width: 0` on the middle, `flex-shrink: 0` on the action |
|
|
120
|
+
| Badge wraps onto two lines | Badge allowed to shrink | `white-space: nowrap; flex-shrink: 0`, decide what yields |
|
|
121
|
+
| Avatar adrift beside a three-line name | `align-items: center` on rows of varying height | Top-align once text can wrap |
|
|
122
|
+
| Last row cut mid-glyph | Fixed height without scroll affordance | Visible scroll or a fade, intended `overflow` |
|
|
123
|
+
| Heading breaks mid-word | `word-break: break-all` | `overflow-wrap: anywhere` |
|
|
124
|
+
| Wrong initials | `split(' ')[0][0]` | Grapheme-aware initials (`Intl.Segmenter`), first and last word, icon fallback |
|
|
125
|
+
| Orphaned dash where an optional field was | Placeholder for a missing value | Omit the line, or reserve its height on purpose |
|
|
126
|
+
| "1 members" | Hard-coded plural | `Intl.PluralRules` or the project's i18n |
|
|
127
|
+
| Numbers jitter as they update | Proportional figures | `tabular-nums` |
|
|
128
|
+
| `1284`, `NaN`, `undefined` | Raw number rendered | `Intl.NumberFormat`, guard null |
|
|
129
|
+
| Translated button label overflows | Fixed width | Width from content with a `min-width` |
|
|
130
|
+
| Tall scripts clipped | Tight line height with hidden overflow | Looser line height, no clipping on text |
|
|
131
|
+
| Broken-image icon | No error fallback | Fall back to initials or a neutral icon |
|
|
132
|
+
| Truncated text with no way to read it | Ellipsis only | Title or tooltip, and the full value in a detail view |
|
|
133
|
+
| 1,000 rows stutter | Every row rendered | Virtualize or paginate |
|
|
134
|
+
| Raw `<b>`, `&` or `**text**` shown | Wrong escaping layer | Escape once at render; never inject user HTML |
|
|
135
|
+
|
|
136
|
+
## Truncate, wrap or clamp
|
|
137
|
+
|
|
138
|
+
Decide per field:
|
|
139
|
+
|
|
140
|
+
- **Wrap** what identifies something (names, titles in a detail view). Two lines is fine; four means the column is too narrow.
|
|
141
|
+
- **Truncate at the end** for secondary metadata where the start carries the meaning (role, preview text), always with a way to see the full value.
|
|
142
|
+
- **Truncate in the middle** where items differ at the end (file names, paths, hashes, emails sharing a long domain).
|
|
143
|
+
- **Clamp** (`line-clamp: 2`) multi-line previews in cards so heights stay predictable.
|
|
144
|
+
- **Never truncate** numbers, amounts, dates, or anything people compare.
|
|
145
|
+
|
|
146
|
+
## Output
|
|
147
|
+
|
|
148
|
+
| # | Severity | Field | Worst-case value | What happens | Fix (`file:line`) |
|
|
149
|
+
|---|---|---|---|---|---|
|
|
150
|
+
| 1 | Broken | email | `bartholomew.fitzgerald@...example.com` | Menu unreachable at 400px | `min-width: 0` on text column, `overflow-wrap: anywhere` on email |
|
|
151
|
+
| 2 | Ugly | count | 1 | "1 members" | Plural rules |
|
|
152
|
+
| 3 | Fragile | name | n/a | No max length in schema or form | Matching limits in both |
|
|
153
|
+
|
|
154
|
+
Severity: **Broken** (unreadable, unreachable, wrong data), **Ugly** (readable but visibly wrong), **Fragile** (fine now, one realistic step from breaking). Also list what held up, so the test is visibly real.
|
|
155
|
+
|
|
156
|
+
In a code step, fix the breaks; where there are several right answers (truncate or wrap, what an empty role shows), choose by the rules above, apply it, and list the choice in the report. In a review step, report only.
|
|
157
|
+
|
|
158
|
+
Sources: adapted from emilkowalski/skills break-ui SKILL.md and CATALOG.md (MIT, changed: no interactive toggle shipped, no "report and wait for the user" step; code steps fix, review steps report) and pbakaus/impeccable harden reference (Apache-2.0, changed: rewritten).
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: motion-design
|
|
3
|
+
description: Use when adding, changing or reviewing UI animation or transitions: press and hover feedback, menus, dialogs, drawers, toasts, list changes, view transitions, marketing-page motion. Covers whether to animate at all, durations, easing, performance, interruption and reduced motion, CSS first, then the project's motion library.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Motion design
|
|
7
|
+
|
|
8
|
+
Motion should be felt, not watched. Good motion explains a change (where the menu came from, that the click registered, what was removed) and then gets out of the way. Generated UI tends to do the opposite: a fade-up on every block, hover lifts on every card, bounce easing, 400ms dropdowns.
|
|
9
|
+
|
|
10
|
+
The project's motion tokens, existing transitions and installed animation library win over every value here. If the codebase already animates dialogs at 200ms with its own curve, new dialogs do the same.
|
|
11
|
+
|
|
12
|
+
## Decide before you animate
|
|
13
|
+
|
|
14
|
+
### 1. Should this animate at all?
|
|
15
|
+
|
|
16
|
+
| How often people see it | Decision |
|
|
17
|
+
|---|---|
|
|
18
|
+
| 100+ times a day (keyboard shortcuts, command palette, typing, list navigation by keys) | No animation |
|
|
19
|
+
| Tens of times a day (hover, tab switches, row selection) | Instant, or a 100 to 150ms colour or opacity change |
|
|
20
|
+
| Occasionally (menus, dialogs, drawers, toasts, expanding sections) | Standard animation |
|
|
21
|
+
| Rarely (first run, a completed setup, an empty state the first time) | May carry a little more character |
|
|
22
|
+
|
|
23
|
+
Never animate keyboard-initiated actions. Product screens load into the task: no orchestrated page-load sequences.
|
|
24
|
+
|
|
25
|
+
### 2. What is it for?
|
|
26
|
+
|
|
27
|
+
Valid purposes: feedback (the press registered), state (open, selected, saved), spatial continuity (the toast leaves the way it came, the drawer slides from its edge), preventing a jarring jump (content appearing or disappearing), and on marketing pages, explaining how something works. "It looks nice" is not a purpose for something seen often.
|
|
28
|
+
|
|
29
|
+
### 3. Easing
|
|
30
|
+
|
|
31
|
+
- Entering or exiting: **ease-out**. Default curve `cubic-bezier(0.23, 1, 0.32, 1)`.
|
|
32
|
+
- Moving or morphing on screen: **ease-in-out**, `cubic-bezier(0.77, 0, 0.175, 1)`.
|
|
33
|
+
- Hover and colour changes: `ease`.
|
|
34
|
+
- Constant motion (progress, marquee): `linear`.
|
|
35
|
+
- Drawers and sheets: `cubic-bezier(0.32, 0.72, 0, 1)` reads like native mobile sheets.
|
|
36
|
+
- Never `ease-in` for UI: it delays the first frame, exactly when the person is watching. No bounce or elastic curves by reflex.
|
|
37
|
+
|
|
38
|
+
### 4. Duration
|
|
39
|
+
|
|
40
|
+
| Element | Duration |
|
|
41
|
+
|---|---|
|
|
42
|
+
| Press feedback | 100 to 160ms |
|
|
43
|
+
| Hover, colour | 100 to 150ms |
|
|
44
|
+
| Tooltip, small popover | 125 to 200ms |
|
|
45
|
+
| Dropdown, select, menu | 150 to 250ms |
|
|
46
|
+
| Dialog, drawer, sheet | 200 to 300ms (large drawers up to 500ms) |
|
|
47
|
+
| Layout or view transition | 250 to 400ms |
|
|
48
|
+
| Authored moment on a marketing page | 500 to 800ms |
|
|
49
|
+
|
|
50
|
+
UI motion stays under 300ms. Exits are faster and quieter than entrances (about 75% of the enter time, a small offset rather than the full distance).
|
|
51
|
+
|
|
52
|
+
## Rules
|
|
53
|
+
|
|
54
|
+
1. **Animate `transform` and `opacity`.** They skip layout and paint. Animate `width`, `height`, `top`, `left` or margins only with a technique built for it (FLIP, grid-template-rows `0fr` to `1fr`, View Transitions). `filter`, `clip-path` and shadows are fine on small, bounded elements.
|
|
55
|
+
2. **Never `transition: all`.** Name the properties.
|
|
56
|
+
3. **Interruptible by default.** Use CSS transitions (or springs) for anything the person can trigger again mid-way; they retarget. Keyframes restart from zero; keep them for one-shot sequences.
|
|
57
|
+
4. **Nothing appears from nothing.** Enter from `scale(0.95)` to `scale(0.97)` with opacity 0, never `scale(0)`.
|
|
58
|
+
5. **Origin-aware popovers.** Menus, popovers and tooltips scale from their trigger (`transform-origin` at the trigger side). Dialogs stay centred.
|
|
59
|
+
6. **Stagger only real lists**, 30 to 80ms between items, total under about 300ms, and never block interaction while it plays.
|
|
60
|
+
7. **Press feedback**: `scale(0.97)` on `:active` for buttons (not below 0.95).
|
|
61
|
+
8. **Tooltips**: a short delay before the first one; once one is open, neighbours open instantly with no animation.
|
|
62
|
+
9. **Hover motion only for fine pointers**: wrap it in `@media (hover: hover) and (pointer: fine)`.
|
|
63
|
+
10. **Content is visible by default.** If a script fails or an observer never fires, nothing stays hidden. Scroll reveals start from an already-visible state or have a no-script fallback.
|
|
64
|
+
11. **Reduced motion means fewer and gentler, not none.** Under `prefers-reduced-motion: reduce`, remove movement, scaling and parallax; keep short opacity and colour changes that confirm state. Stop decorative loops. Autoplaying motion over 5 seconds needs a pause control.
|
|
65
|
+
12. **Motion is never the only signal.** Every animated state change also has a static cue (colour, icon, text).
|
|
66
|
+
13. **`will-change` only to cure an observed first-frame stutter**, on the element and only during the animation.
|
|
67
|
+
14. **No new dependency for an effect the stack can already express.** CSS transitions, `@starting-style`, the Web Animations API and View Transitions cover most UI needs.
|
|
68
|
+
|
|
69
|
+
## Implement with what the project has
|
|
70
|
+
|
|
71
|
+
In order: CSS transitions and keyframes; the Web Animations API for programmatic control; the project's existing library (Vue `<Transition>`, Svelte transitions, Angular animations, Motion for React, GSAP); View Transitions for continuity between states or routes. Recipes for each are in [references/recipes.md](references/recipes.md).
|
|
72
|
+
|
|
73
|
+
## Marketing pages
|
|
74
|
+
|
|
75
|
+
A page read once may carry one authored moment: a hero sequence, one interactive demo, a single scroll-linked reveal whose scroll relationship means something. One strong idea beats an identical fade-up on every section. Keep it under about 800ms, make it skippable by scrolling, give it a reduced-motion version, and keep the page complete without it.
|
|
76
|
+
|
|
77
|
+
## Check
|
|
78
|
+
|
|
79
|
+
- Play it at slow speed (DevTools Animations panel at 10%, or multiply durations by 5 temporarily). Look for two states overlapping in a crossfade, wrong origins, properties out of sync, a jump at the end.
|
|
80
|
+
- Trigger it twice quickly and mid-way: it reverses smoothly, nothing sticks.
|
|
81
|
+
- Toggle reduced motion (DevTools rendering panel, or Playwright `reducedMotion: 'reduce'`): state changes still read.
|
|
82
|
+
- Throttle the CPU 4x: no dropped frames on the common path.
|
|
83
|
+
- A screenshot taken mid-load shows no blank bands where a reveal did not fire.
|
|
84
|
+
|
|
85
|
+
## Common mistakes
|
|
86
|
+
|
|
87
|
+
| Mistake | Fix |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `transition: all 300ms` | `transition: transform 200ms var(--ease-out), opacity 200ms var(--ease-out)` |
|
|
90
|
+
| `scale(0)` entrance | `scale(0.95)` with opacity 0 |
|
|
91
|
+
| `ease-in` on a dropdown | ease-out, custom curve |
|
|
92
|
+
| Popover scaling from its centre | `transform-origin` at the trigger |
|
|
93
|
+
| Animation on a keyboard shortcut | Remove it |
|
|
94
|
+
| 400ms dropdown | 150 to 250ms |
|
|
95
|
+
| Keyframes on a toggle that can be re-triggered | A transition |
|
|
96
|
+
| Hover scale that fires on tap | Gate with the hover and pointer media query |
|
|
97
|
+
| Every section fades up on scroll | Remove; one authored moment at most, on marketing pages |
|
|
98
|
+
| Exit as slow as the enter | Shorter, smaller exit |
|
|
99
|
+
|
|
100
|
+
## Output in reviews
|
|
101
|
+
|
|
102
|
+
A table with **Before**, **After** and **Why**, one row per issue, citing `file:line`.
|
|
103
|
+
|
|
104
|
+
Related: `interface-polish`, `interface-design`, `accessibility`.
|
|
105
|
+
|
|
106
|
+
Sources: adapted from emilkowalski/skills emil-design-eng (MIT, changed: framework-neutral, React and Motion specifics moved to examples, "initial response" step removed), jakubkrehel/make-interfaces-feel-better animations reference (MIT) and pbakaus/impeccable animate and operate references (Apache-2.0, changed: rewritten, ask steps removed).
|