Design-engineering craft for interfaces that feel polished, fast, and physical.
Great interfaces are rarely one big thing. They are an accumulation of small, individually invisible details that compound: felt, not seen. This skill encodes those details as concrete, committed defaults so the work is consistent instead of vibes. The numbers here are opinionated starting points; commit to them, then trust your eyes: a value is right when it feels right.
How to use this skill
When building UI, apply these principles by default: you don't need to be asked. Reach for the motion tokens below instead of inventing one-off durations.
When reviewing or "making something feel better", default to flagging: approval is earned. Run the escalation triggers, return the review output contract (every change, grouped, before/after, never a subset), close with a verdict, and confirm feel with the feel check.
When auditing a whole codebase (not a single diff), use audit mode: survey the surface, rank findings by leverage, and write self-contained plans a cheaper model can execute without touching your source.
Load a reference file (linked throughout) when you need the long-form rationale, the good-vs-bad code, or the decision tables. SKILL.md alone has every number you need to act.
Framework-agnostic first. Examples lead with vanilla CSS / the Web Animations API. Tailwind and Motion (motion/framer-motion) variants follow. Don't add an animation dependency that isn't already in package.json: check first.
The first rule: motion serves a purpose
If you can't name what an animation communicates (causality, status, spatial continuity, or deliberate delight), cut it. The best animation is often no animation.
Never animate high-frequency or keyboard-initiated actions. Opening a menu you open 100×/day, deleting a list item, tabbing through fields: these must feel instant. Animation there is a tax, not a delight.
Motion's jobs: show cause and effect, give feedback, preserve the user's spatial map (where did this come from, where did it go), and, sparingly, add character.
Motion tokens (the shared contract)
Every animation in the product should pull from one small scale. Inconsistent, hand-picked durations are the single biggest tell of an amateur interface. Define these once at :root and reference them everywhere.
:root {
/* Duration: most UI lives in 150–300ms. Scale UP with travel distance / surface size. */
--duration-instant: 0ms; /* keyboard + high-frequency actions: no animation */
--duration-fast: 150ms; /* micro: hover, press, color, icon tint/recolor */
--duration-base: 200ms; /* standard: toggles, small reveals, tabs */
--duration-slow: 300ms; /* modals, drawers, popovers, larger surfaces */
--duration-slower: 450ms; /* full-screen / large-travel transitions */
/* Easing: chosen by DIRECTION of travel. Custom curves beat the weak built-in keywords. */
--ease-standard: cubic-bezier(0.2, 0, 0, 1); /* small in-place state changes (default) */
--ease-out: cubic-bezier(0.05, 0.7, 0.1, 1); /* ENTERING / decelerating: settles into place */
--ease-in: cubic-bezier(0.3, 0, 0.8, 0.15); /* EXITING / accelerating: leaves decisively */
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); /* moving ACROSS screen / morphing in place */
--ease-ios: cubic-bezier(0.32, 0.72, 0, 1); /* drawers / sheets / sliding panels */
/* linear is ONLY for continuous loops: spinners, marquees, progress. Never for discrete UI. */
}
Three rules that make the tokens work:
Exit faster than enter: roughly one tier down (a 300ms enter → ~200ms exit). Things should leave more quickly and quietly than they arrive.
Duration scales with distance and size. A tooltip is --duration-fast; a full-screen route transition is --duration-slower. Same easing family, longer time.
Direction picks the easing. Entering → --ease-out. Leaving → --ease-in. Repositioning something already on screen → --ease-in-out.
Springs (for gesture-driven or physical motion). Specify response and bounce, not a fixed duration: "nothing in the real world changes instantly." Keep bounce low for functional UI.
Avoid springs where precision matters more than feel.
Distances: enter from translateY(8–12px), never from far away. Never animate from scale(0): it looks like it teleports in; start from ~`0.9. Press feedback is scale(0.96): never below 0.95` (feels exaggerated).
→ Full motion guidance, enter/exit recipes, interruptibility, origin-awareness, and modern primitives: motion.md
Core principles
Motion → motion.md
Purpose over decoration; don't animate high-frequency/keyboard actions.
Animate only transform, opacity, filter: never layout properties. Never transition: all.
Use the token scale; exit faster than enter; duration scales with size.
Easing by direction:--ease-out enter, --ease-in exit, --ease-in-out on-screen movement.
Make animations interruptible: CSS transitions (not keyframes) for stateful UI; they retarget mid-flight. Keyframes only for one-shot staged sequences.
Origin-aware: set transform-origin so things grow from their trigger (a dropdown opens from its button).
Press feedback scale(0.96); stagger groups ~`100ms, words ~80ms`.
Gestures → gestures.md
Drag, swipe, sheets, drag-to-reorder: track the finger 1:1, hand off the release velocity into the settle spring, stay interruptible (animate from the live value), and rubber-band at edges instead of hard-stopping.
States & feedback → interaction-states.md
8. Every interactive element needs hover, active, and :focus-visible: but gate hover behind @media (hover: hover) so it doesn't stick on touch.
9. Feedback is immediate and trigger-local: an inline checkmark on "copied," not a toast across the screen. Toggles take effect instantly; disable submit buttons after submit.
10. Design the unhappy paths: loading (skeletons, with a ~150–300ms show-delay and ~300–500ms minimum visible time to avoid flicker), empty, error, disabled.
11. Optimistic UI: render the result immediately, reconcile/roll back on the server response. Responsiveness must never depend on network latency.
Surfaces & depth → surfaces.md
12. Concentric radius:outer = inner + padding. Mismatched nested radii are the most common "off" tell.
13. Shadows for elevation, not dividers. Light comes from above; layer shadows and tint them with the surface hue: never pure black. Use real borders for dividers, table cells, and input outlines.
14. Optical > mathematical alignment: icon-side button padding is ~2px less than the text side; nudge play triangles ~2px right; fix asymmetric glyphs in the SVG.
15. Hit area ≥ 44×44px (≥40 acceptable); extend small controls with a pseudo-element. Never overlap two hit areas.
Translucent materials (glass chrome via backdrop-filter): material weight encodes hierarchy, never stack two light glass layers, and solidify under prefers-reduced-transparency.
Typography → typography.md
16. Hierarchy via weight and color, not size alone. Three text colors (primary/secondary/tertiary); never pure black. Body & inputs ≥16px (inputs <16px trigger iOS zoom).
17. tabular-nums for any number that changes in place (timers, prices, counters) to stop layout shift. text-wrap: balance on headings, pretty on body. Antialias once at the root. Never change font-weight on hover (layout shift).
Layout & color → layout-and-color.md
18. Spacing signals grouping: more space between groups, less within. Use a constrained scale on a 4/8px base. Align everything to something. No dead zones between list items: extend padding, not gaps.
19. Near-black/near-white, saturated neutrals, functional color scales. Prefer fewer borders (use spacing, background, shadow). Color carries meaning (red danger, green success) but never alone.
Performance → performance.md
20. transform/opacity/filter run on the GPU compositor; everything else costs layout/paint and drops frames. Target 60fps. will-change only just-in-time and sparingly. Tool ladder: CSS → Web Animations API → JS library.
Accessibility (never optional) → accessibility.md
21. Respect prefers-reduced-motion for every animation. Reduced motion ≠ no motion: substitute a fade or instant change, don't just delete meaning. Opt motion in via (prefers-reduced-motion: no-preference). Never convey information by motion or color alone. Focus rings via box-shadow (respects radius). Semantics before ARIA; aria-label on icon-only controls.
Modern primitives worth reaching for
Before hand-rolling, check whether a platform primitive does it better (degrade gracefully):
View Transitions API (document.startViewTransition, view-transition-name): shared-element and route transitions, the cases people used to fake with FLIP.
@starting-style + transition-behavior: allow-discrete: CSS-native enter animations for popovers/dialogs/display:none, no JS.
Scroll-driven animations (animation-timeline: scroll()/view()): scroll progress without scroll listeners.
Name the exact properties: transition: transform 150ms, opacity 150ms
Animating width/height/top/margin
Animate transform/opacity; use FLIP or View Transitions for layout changes
Same duration for enter and exit
Exit ~one tier faster than enter
linear easing on UI
--ease-out to enter, --ease-in to exit
Keyframes for hover/toggle/open
CSS transitions: they interrupt and retarget mid-flight
Animating from scale(0) / opacity only
Start from ~`scale(0.9)+ smalltranslateY`; combine transform + opacity
No prefers-reduced-motion
Wrap motion; substitute a fade or instant change
Removing the focus outline for looks
Replace with a :focus-visiblebox-shadow ring
Hover state stuck on mobile
Gate with @media (hover: hover)
Numbers jumping width as they change
font-variant-numeric: tabular-nums
Mismatched nested corner radii
outer = inner + padding
Spinner during a 120ms fetch
Optimistic update; or show-delay the loader 150–300ms
Fixed desktop-only grid that shatters at other widths
Fluid by default (auto-fit/minmax + a breakpoint floor): design for the stated context, degrade gracefully outside it
Colored left/top accent bar on a card or alert
Tinted background + semantic glyph + words carry severity; the stripe is a generated-UI tell (only the active-tab underline earns an accent edge)
Cards in one row with ragged heights
Rows share a height: keep align-items: stretch and flex the card body
Em dashes in UI copy ("Signups: Apr")
Middot in labels ("Signups · Apr"), colon for key-value pairs; no em dashes anywhere in the interface
Marketing-slop microcopy ("Unleash your workflow", "Oops!", emoji in headings)
Buttons name the action ("Save changes"); errors state cause + recovery; one word per referent; no emoji or exclamation standing in for a voice
Chart story only reachable via hover, or no hover at all
Story lands with zero interaction (direct labels, threshold bands, annotations); points still reward hover with a tooltip, gated
Review output contract
When reviewing code or "making it feel better," return findings as before/after tables grouped by area (Motion, States, Surfaces, Typography, Layout/Color, Performance, Accessibility). For each change: the file/element, the before, the after, and one line of why. Report every change you'd make, not a subset: omit only the empty groups. Lead with a one-line summary of the highest-impact fix.
Review checklist
Motion pulls from the token scale; no one-off durations
Only transform/opacity/filter animated; no transition: all
Enter uses --ease-out, exit uses --ease-in and is faster
Stateful animations are interruptible (transitions, not keyframes)
transform-origin set so motion grows from its source
Press feedback (scale(0.96)), gated hover, visible :focus-visible ring
Loading/empty/error/disabled states exist; loaders are flicker-guarded
Optimistic updates where an action would otherwise wait on the network
Concentric radii; shadows for elevation (tinted, layered), borders for dividers
Optical alignment on icons/buttons; hit areas ≥ 40–44px
Hierarchy via weight + color; tabular-nums; body/inputs ≥16px; balanced headings
Spacing groups related items; everything aligned; constrained scale
prefers-reduced-motion honored with substitutions, not deletions
Information never conveyed by motion or color alone
UI copy reads human: buttons name the action, errors state cause + recovery, labels are consistent, no marketing puffery or emoji headings
Escalation triggers (flag on sight)
The high-severity tells. Any one of these is a finding, not a maybe:
transition: all, or animating a layout property (width/height/top/left/margin/padding).
ease-in or a weak built-in easing on anything entering or on a general state change; linear on discrete UI.
Any animation on a keyboard-initiated or 100+/day action.
A UI transition over ~300ms with no stated reason, or the same duration for enter and exit.
scale(0), or an opacity-only entrance with no translate.
transform-origin: center or unset on a trigger-anchored popover/dropdown/tooltip (modals stay centered, that is exempt).
Keyframes on toasts, toggles, or anything triggered rapidly or reversibly.
Framer Motion x/y/scale shorthands on motion that runs while the page is busy; a CSS variable on a parent driving child transforms.
Missing prefers-reduced-motion; ungated :hover motion; a focus outline removed with no replacement.
A colored accent stripe on a card or alert; an em dash in UI copy.
Remedial hierarchy (prefer earlier moves)
When you propose a fix, prefer the earliest move that solves it:
Delete the animation (high-frequency, no purpose, keyboard-triggered).
Reduce it (shorter, smaller travel, fewer animated properties).
Fix the easing (ease-in → --ease-out/custom curve; weak keyword → an authored curve).
Fix origin and physicality (transform-origin; scale(0) → scale(0.9) + opacity).
Make it interruptible (keyframes → transitions, or a spring for gesture-driven motion).
Move it to the GPU (layout props → transform/opacity; shorthand → full transform string).
Asymmetric timing (slow the deliberate phase, snap the system response).
Polish (blur to mask a crossfade, stagger a group, @starting-style for entry).
Accessibility and cohesion (reduced-motion + hover gating; match the product's personality).
The verdict
Close every review with an explicit decision:
Block if there is any feel-breaking regression: ease-in/sluggish easing on UI, scale(0), animation on a keyboard or high-frequency action, a non-GPU animation with an easy GPU fix, or a removed focus ring.
Approve only when none of those are present, durations and easing are in bounds, stateful motion is interruptible, and reduced-motion is honored. Approval is earned, not the default. "The motion here is already right" is a valid result.
Feel check
Motion can be mechanically correct and still feel wrong; verify feel, not just code, before calling it done:
Slow-mo. Play it at ~10% (DevTools Animations panel) and watch for coordinated properties drifting out of sync, an easing that stalls, or a wrong transform-origin.
Frame-by-frame. Step through a crossfade or icon swap; two states double-exposed means it needs blur or a tuned curve.
Real device for gestures. Test drags, swipes, and sheets on actual touch hardware (gestures.md), not just a desktop pointer.
Fresh eyes. Review the motion the next day. Imperfections invisible while building surface later.
Audit mode: survey and plan
For a whole codebase rather than a single diff, run finesse as an audit: spend the capable model's judgement on understanding the motion and deciding what is worth fixing, and hand execution to any agent, including cheaper models. It plans; it does not patch.
Recon. Map the surface first: framework, motion libraries (Motion, GSAP, plain CSS, WAAPI), component libraries (Radix, Base UI, shadcn/ui), where motion and tokens live, the product's personality, and a frequency map (what is hit 100+/day vs occasionally vs rarely). Frequency drives severity.
Audit in parallel against finesse's areas: Motion, Gestures, States, Surfaces, Typography, Layout & color, Performance, Accessibility. For anything past a small repo, fan out read-only subagents, one per area (or per app region), each returning findings only (file:line + evidence, no fixes).
Vet and rank. Re-read the cited code for every finding yourself; reject anything by-design or exempt (transform-origin: center on a modal is correct). Present the survivors as one table ordered by leverage (impact over effort), with a severity per row.
Plan, do not patch. Never edit source in this mode. For each selected finding write a self-contained plan into plans/NNN-*.md using PLAN-TEMPLATE.md, stamped with the commit, with a repo-conventions section and a mandatory feel check. Then write plans/README.md: order, dependencies, status. Any agent can execute a plan afterward with zero context.
Severity: HIGH = feel-breaking (wrong easing on UI, animation on keyboard/high-frequency actions, dropped frames, scale(0)); MEDIUM = noticeably off (wrong origin, non-interruptible dynamic UI, missing reduced-motion); LOW = polish (stagger, blur-masked crossfades, token consolidation).
Model notes
Newer models need fewer rules, not more. Treat this skill as a calibration layer: the tokens and thresholds are committed defaults, not shackles: when a product's design language genuinely demands different values, change the tokens once at :root and say so, rather than scattering one-off exceptions.
Claude Opus 4.8 has strong design instincts with one persistent house default: warm cream/off-white backgrounds (~`#F4F1EA`), serif display type (Georgia, Fraunces, Playfair), italic word-accents, and a terracotta/amber accent. That reads beautifully for editorial, hospitality, and portfolio briefs, and feels wrong for dashboards, dev tools, fintech, healthcare, and enterprise UI. Generic nudges ("don't use cream," "make it clean") just swap in a different fixed palette. Two fixes that work: (1) specify a concrete alternative direction: palette hexes, type family, radius, motion spec; (2) propose 3–4 distinct visual directions first (bg hex / accent hex / typeface + one-line rationale), let the user pick, and build only that one.
Anti-slop guard (any model): avoid the generic-AI tells: Inter/Roboto/Arial/system fonts as display type, purple gradients on white or dark, timid evenly-distributed palettes, cookie-cutter hero layouts. Commit to a cohesive, context-specific direction; dominant colors with sharp accents beat evenly-spread ones.
Claude Fable 5 follows brief instructions reliably and can over-comply with prescriptive checklists: over-prescription degrades its output. Apply this skill's judgment, not just its letter: name what each animation communicates, pull from one token scale, honor reduced motion. If a recipe fights the product's design language, adapt it deliberately and note the deviation.
Reference files
File
Read it for
motion.md
Duration/easing/spring tokens in depth, enter/exit recipes, interruptibility, origin, stagger, clip-path reveals, 3D transforms, cohesion, when-NOT-to-animate, modern primitives
Reverse glossary: turn a described effect ("the springy popover thing") into the precise term to ask for
PLAN-TEMPLATE.md
The self-contained plan format audit mode writes for a cheaper model to execute
1---2name: finesse3description: Finesse4---56# Finesse78Design-engineering craft for interfaces that feel polished, fast, and physical.910Great interfaces are rarely one big thing. They are an accumulation of small, individually invisible details that compound: *felt, not seen*. This skill encodes those details as concrete, committed defaults so the work is consistent instead of vibes. The numbers here are opinionated starting points; commit to them, then trust your eyes: a value is right when it *feels* right.1112## How to use this skill1314- **When building UI**, apply these principles by default: you don't need to be asked. Reach for the motion tokens below instead of inventing one-off durations.15- **When reviewing or "making something feel better"**, default to flagging: approval is earned. Run the [escalation triggers](#escalation-triggers-flag-on-sight), return the [review output contract](#review-output-contract) (every change, grouped, before/after, never a subset), close with a [verdict](#the-verdict), and confirm feel with the [feel check](#feel-check).16- **When auditing a whole codebase** (not a single diff), use [audit mode](#audit-mode-survey-and-plan): survey the surface, rank findings by leverage, and write self-contained [plans](PLAN-TEMPLATE.md) a cheaper model can execute without touching your source.17- **Load a reference file** (linked throughout) when you need the long-form rationale, the good-vs-bad code, or the decision tables. `SKILL.md` alone has every number you need to *act*.18- **Framework-agnostic first.** Examples lead with vanilla CSS / the Web Animations API. Tailwind and Motion (`motion`/`framer-motion`) variants follow. Don't add an animation dependency that isn't already in `package.json`: check first.1920## The first rule: motion serves a purpose2122If you can't name what an animation communicates (causality, status, spatial continuity, or deliberate delight), cut it. **The best animation is often no animation.**2324- **Never animate high-frequency or keyboard-initiated actions.** Opening a menu you open 100×/day, deleting a list item, tabbing through fields: these must feel instant. Animation there is a tax, not a delight.25- **Motion's jobs:** show cause and effect, give feedback, preserve the user's spatial map (where did this come from, where did it go), and, sparingly, add character.2627## Motion tokens (the shared contract)2829Every animation in the product should pull from one small scale. Inconsistent, hand-picked durations are the single biggest tell of an amateur interface. Define these once at `:root` and reference them everywhere.3031```css32:root {33 /* Duration: most UI lives in 150–300ms. Scale UP with travel distance / surface size. */34 --duration-instant: 0ms; /* keyboard + high-frequency actions: no animation */35 --duration-fast: 150ms; /* micro: hover, press, color, icon tint/recolor */36 --duration-base: 200ms; /* standard: toggles, small reveals, tabs */37 --duration-slow: 300ms; /* modals, drawers, popovers, larger surfaces */38 --duration-slower: 450ms; /* full-screen / large-travel transitions */3940 /* Easing: chosen by DIRECTION of travel. Custom curves beat the weak built-in keywords. */41 --ease-standard: cubic-bezier(0.2, 0, 0, 1); /* small in-place state changes (default) */42 --ease-out: cubic-bezier(0.05, 0.7, 0.1, 1); /* ENTERING / decelerating: settles into place */43 --ease-in: cubic-bezier(0.3, 0, 0.8, 0.15); /* EXITING / accelerating: leaves decisively */44 --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); /* moving ACROSS screen / morphing in place */45 --ease-ios: cubic-bezier(0.32, 0.72, 0, 1); /* drawers / sheets / sliding panels */46 /* linear is ONLY for continuous loops: spinners, marquees, progress. Never for discrete UI. */47}48```4950**Three rules that make the tokens work:**51521. **Exit faster than enter**: roughly one tier down (a 300ms enter → ~200ms exit). Things should leave more quickly and quietly than they arrive.532. **Duration scales with distance and size.** A tooltip is `--duration-fast`; a full-screen route transition is `--duration-slower`. Same easing family, longer time.543. **Direction picks the easing.** Entering → `--ease-out`. Leaving → `--ease-in`. Repositioning something already on screen → `--ease-in-out`.5556**Springs** (for gesture-driven or physical motion). Specify *response* and *bounce*, not a fixed duration: "nothing in the real world changes instantly." Keep bounce low for functional UI.57- Functional (icon swap, toggle): `{ type: "spring", duration: 0.3, bounce: 0 }`58- Physical (sheets, playful): `{ type: "spring", duration: 0.5, bounce: 0.2 }`59- Avoid springs where precision matters more than feel.6061**Distances:** enter from `translateY(8–12px)`, never from far away. **Never animate from `scale(0)`**: it looks like it teleports in; start from ~`0.9`. Press feedback is `scale(0.96)`: never below `0.95` (feels exaggerated).6263→ Full motion guidance, enter/exit recipes, interruptibility, origin-awareness, and modern primitives: **[motion.md](motion.md)**6465## Core principles6667**Motion** → [motion.md](motion.md)681. Purpose over decoration; don't animate high-frequency/keyboard actions.692. Animate **only `transform`, `opacity`, `filter`**: never layout properties. Never `transition: all`.703. Use the **token scale**; exit faster than enter; duration scales with size.714. **Easing by direction:** `--ease-out` enter, `--ease-in` exit, `--ease-in-out` on-screen movement.725. **Make animations interruptible**: CSS *transitions* (not keyframes) for stateful UI; they retarget mid-flight. Keyframes only for one-shot staged sequences.736. **Origin-aware:** set `transform-origin` so things grow from their trigger (a dropdown opens from its button).747. Press feedback `scale(0.96)`; stagger groups ~`100ms`, words ~`80ms`.7576**Gestures** → [gestures.md](gestures.md)77- Drag, swipe, sheets, drag-to-reorder: track the finger **1:1**, hand off the release velocity into the settle spring, stay interruptible (animate from the live value), and **rubber-band** at edges instead of hard-stopping.7879**States & feedback** → [interaction-states.md](interaction-states.md)808. Every interactive element needs **hover, active, and `:focus-visible`**: but gate hover behind `@media (hover: hover)` so it doesn't stick on touch.819. **Feedback is immediate and trigger-local:** an inline checkmark on "copied," not a toast across the screen. Toggles take effect instantly; disable submit buttons after submit.8210. **Design the unhappy paths:** loading (skeletons, with a ~150–300ms show-delay and ~300–500ms minimum visible time to avoid flicker), empty, error, disabled.8311. **Optimistic UI:** render the result immediately, reconcile/roll back on the server response. Responsiveness must never depend on network latency.8485**Surfaces & depth** → [surfaces.md](surfaces.md)8612. **Concentric radius:** `outer = inner + padding`. Mismatched nested radii are the most common "off" tell.8713. **Shadows for elevation, not dividers.** Light comes from above; layer shadows and tint them with the surface hue: never pure black. Use real borders for dividers, table cells, and input outlines.8814. **Optical > mathematical alignment:** icon-side button padding is ~2px less than the text side; nudge play triangles ~2px right; fix asymmetric glyphs in the SVG.8915. **Hit area ≥ 44×44px** (≥40 acceptable); extend small controls with a pseudo-element. Never overlap two hit areas.90- **Translucent materials** (glass chrome via `backdrop-filter`): material weight encodes hierarchy, never stack two light glass layers, and solidify under `prefers-reduced-transparency`.9192**Typography** → [typography.md](typography.md)9316. **Hierarchy via weight and color, not size alone.** Three text colors (primary/secondary/tertiary); never pure black. Body & inputs **≥16px** (inputs <16px trigger iOS zoom).9417. **`tabular-nums`** for any number that changes in place (timers, prices, counters) to stop layout shift. **`text-wrap: balance`** on headings, **`pretty`** on body. Antialias once at the root. Never change font-weight on hover (layout shift).9596**Layout & color** → [layout-and-color.md](layout-and-color.md)9718. **Spacing signals grouping:** more space between groups, less within. Use a constrained scale on a 4/8px base. Align everything to something. No dead zones between list items: extend padding, not gaps.9819. **Near-black/near-white, saturated neutrals, functional color scales.** Prefer fewer borders (use spacing, background, shadow). Color carries meaning (red danger, green success) but never *alone*.99100**Performance** → [performance.md](performance.md)10120. **`transform`/`opacity`/`filter` run on the GPU compositor; everything else costs layout/paint and drops frames.** Target 60fps. `will-change` only just-in-time and sparingly. Tool ladder: CSS → Web Animations API → JS library.102103**Accessibility (never optional)** → [accessibility.md](accessibility.md)10421. **Respect `prefers-reduced-motion` for every animation.** Reduced motion ≠ no motion: *substitute* a fade or instant change, don't just delete meaning. Opt motion in via `(prefers-reduced-motion: no-preference)`. Never convey information by motion or color alone. Focus rings via `box-shadow` (respects radius). Semantics before ARIA; `aria-label` on icon-only controls.105106## Modern primitives worth reaching for107108Before hand-rolling, check whether a platform primitive does it better (degrade gracefully):109- **View Transitions API** (`document.startViewTransition`, `view-transition-name`): shared-element and route transitions, the cases people used to fake with FLIP.110- **`@starting-style` + `transition-behavior: allow-discrete`**: CSS-native enter animations for popovers/dialogs/`display:none`, no JS.111- **Scroll-driven animations** (`animation-timeline: scroll()/view()`): scroll progress without scroll listeners.112- **`interpolate-size: allow-keywords`**: animate to/from `height: auto`.113114## Common mistakes115116| Mistake | Fix |117| --- | --- |118| `transition: all` | Name the exact properties: `transition: transform 150ms, opacity 150ms` |119| Animating `width`/`height`/`top`/`margin` | Animate `transform`/`opacity`; use FLIP or View Transitions for layout changes |120| Same duration for enter and exit | Exit ~one tier faster than enter |121| `linear` easing on UI | `--ease-out` to enter, `--ease-in` to exit |122| Keyframes for hover/toggle/open | CSS transitions: they interrupt and retarget mid-flight |123| Animating from `scale(0)` / `opacity` only | Start from ~`scale(0.9)` + small `translateY`; combine transform + opacity |124| No `prefers-reduced-motion` | Wrap motion; substitute a fade or instant change |125| Removing the focus outline for looks | Replace with a `:focus-visible` `box-shadow` ring |126| Hover state stuck on mobile | Gate with `@media (hover: hover)` |127| Numbers jumping width as they change | `font-variant-numeric: tabular-nums` |128| Mismatched nested corner radii | `outer = inner + padding` |129| Spinner during a 120ms fetch | Optimistic update; or show-delay the loader 150–300ms |130| Fixed desktop-only grid that shatters at other widths | Fluid by default (`auto-fit`/`minmax` + a breakpoint floor): design for the stated context, degrade gracefully outside it |131| Colored left/top accent bar on a card or alert | Tinted background + semantic glyph + words carry severity; the stripe is a generated-UI tell (only the active-tab underline earns an accent edge) |132| Cards in one row with ragged heights | Rows share a height: keep `align-items: stretch` and flex the card body |133| Em dashes in UI copy ("Signups: Apr") | Middot in labels ("Signups · Apr"), colon for key-value pairs; no em dashes anywhere in the interface |134| Marketing-slop microcopy ("Unleash your workflow", "Oops!", emoji in headings) | Buttons name the action ("Save changes"); errors state cause + recovery; one word per referent; no emoji or exclamation standing in for a voice |135| Chart story only reachable via hover, or no hover at all | Story lands with zero interaction (direct labels, threshold bands, annotations); points still reward hover with a tooltip, gated |136137## Review output contract138139When reviewing code or "making it feel better," return findings as **before/after tables grouped by area** (Motion, States, Surfaces, Typography, Layout/Color, Performance, Accessibility). For each change: the file/element, the before, the after, and one line of why. **Report every change you'd make, not a subset: omit only the empty groups.** Lead with a one-line summary of the highest-impact fix.140141## Review checklist142143- [ ] Motion pulls from the token scale; no one-off durations144- [ ] Only `transform`/`opacity`/`filter` animated; no `transition: all`145- [ ] Enter uses `--ease-out`, exit uses `--ease-in` and is faster146- [ ] Stateful animations are interruptible (transitions, not keyframes)147- [ ] `transform-origin` set so motion grows from its source148- [ ] Press feedback (`scale(0.96)`), gated hover, visible `:focus-visible` ring149- [ ] Loading/empty/error/disabled states exist; loaders are flicker-guarded150- [ ] Optimistic updates where an action would otherwise wait on the network151- [ ] Concentric radii; shadows for elevation (tinted, layered), borders for dividers152- [ ] Optical alignment on icons/buttons; hit areas ≥ 40–44px153- [ ] Hierarchy via weight + color; `tabular-nums`; body/inputs ≥16px; balanced headings154- [ ] Spacing groups related items; everything aligned; constrained scale155- [ ] `prefers-reduced-motion` honored with substitutions, not deletions156- [ ] Information never conveyed by motion or color alone157- [ ] UI copy reads human: buttons name the action, errors state cause + recovery, labels are consistent, no marketing puffery or emoji headings158159## Escalation triggers (flag on sight)160161The high-severity tells. Any one of these is a finding, not a maybe:162163- `transition: all`, or animating a layout property (`width`/`height`/`top`/`left`/`margin`/`padding`).164- `ease-in` or a weak built-in easing on anything entering or on a general state change; `linear` on discrete UI.165- Any animation on a keyboard-initiated or 100+/day action.166- A UI transition over ~300ms with no stated reason, or the same duration for enter and exit.167- `scale(0)`, or an opacity-only entrance with no `translate`.168- `transform-origin: center` or unset on a trigger-anchored popover/dropdown/tooltip (modals stay centered, that is exempt).169- Keyframes on toasts, toggles, or anything triggered rapidly or reversibly.170- Framer Motion `x`/`y`/`scale` shorthands on motion that runs while the page is busy; a CSS variable on a parent driving child transforms.171- Missing `prefers-reduced-motion`; ungated `:hover` motion; a focus outline removed with no replacement.172- A colored accent stripe on a card or alert; an em dash in UI copy.173174## Remedial hierarchy (prefer earlier moves)175176When you propose a fix, prefer the earliest move that solves it:1771781. **Delete** the animation (high-frequency, no purpose, keyboard-triggered).1792. **Reduce** it (shorter, smaller travel, fewer animated properties).1803. **Fix the easing** (`ease-in` → `--ease-out`/custom curve; weak keyword → an authored curve).1814. **Fix origin and physicality** (`transform-origin`; `scale(0)` → `scale(0.9)` + opacity).1825. **Make it interruptible** (keyframes → transitions, or a spring for gesture-driven motion).1836. **Move it to the GPU** (layout props → `transform`/`opacity`; shorthand → full transform string).1847. **Asymmetric timing** (slow the deliberate phase, snap the system response).1858. **Polish** (blur to mask a crossfade, stagger a group, `@starting-style` for entry).1869. **Accessibility and cohesion** (reduced-motion + hover gating; match the product's personality).187188## The verdict189190Close every review with an explicit decision:191192- **Block** if there is any feel-breaking regression: `ease-in`/sluggish easing on UI, `scale(0)`, animation on a keyboard or high-frequency action, a non-GPU animation with an easy GPU fix, or a removed focus ring.193- **Approve** only when none of those are present, durations and easing are in bounds, stateful motion is interruptible, and reduced-motion is honored. Approval is earned, not the default. "The motion here is already right" is a valid result.194195## Feel check196197Motion can be mechanically correct and still feel wrong; verify feel, not just code, before calling it done:198199- **Slow-mo.** Play it at ~10% (DevTools Animations panel) and watch for coordinated properties drifting out of sync, an easing that stalls, or a wrong `transform-origin`.200- **Frame-by-frame.** Step through a crossfade or icon swap; two states double-exposed means it needs blur or a tuned curve.201- **Real device for gestures.** Test drags, swipes, and sheets on actual touch hardware ([gestures.md](gestures.md)), not just a desktop pointer.202- **Fresh eyes.** Review the motion the next day. Imperfections invisible while building surface later.203204## Audit mode: survey and plan205206For a whole codebase rather than a single diff, run finesse as an audit: spend the capable model's judgement on understanding the motion and deciding what is worth fixing, and hand execution to any agent, including cheaper models. It plans; it does not patch.2072081. **Recon.** Map the surface first: framework, motion libraries (Motion, GSAP, plain CSS, WAAPI), component libraries (Radix, Base UI, shadcn/ui), where motion and tokens live, the product's personality, and a **frequency map** (what is hit 100+/day vs occasionally vs rarely). Frequency drives severity.2092. **Audit in parallel** against finesse's areas: Motion, Gestures, States, Surfaces, Typography, Layout & color, Performance, Accessibility. For anything past a small repo, fan out read-only subagents, one per area (or per app region), each returning findings only (`file:line` + evidence, no fixes).2103. **Vet and rank.** Re-read the cited code for every finding yourself; reject anything by-design or exempt (`transform-origin: center` on a modal is correct). Present the survivors as one table ordered by **leverage** (impact over effort), with a severity per row.2114. **Plan, do not patch.** Never edit source in this mode. For each selected finding write a self-contained plan into `plans/NNN-*.md` using [PLAN-TEMPLATE.md](PLAN-TEMPLATE.md), stamped with the commit, with a repo-conventions section and a mandatory [feel check](#feel-check). Then write `plans/README.md`: order, dependencies, status. Any agent can execute a plan afterward with zero context.212213Severity: **HIGH** = feel-breaking (wrong easing on UI, animation on keyboard/high-frequency actions, dropped frames, `scale(0)`); **MEDIUM** = noticeably off (wrong origin, non-interruptible dynamic UI, missing reduced-motion); **LOW** = polish (stagger, blur-masked crossfades, token consolidation).214215## Model notes216217Newer models need fewer rules, not more. Treat this skill as a **calibration layer**: the tokens and thresholds are committed *defaults*, not shackles: when a product's design language genuinely demands different values, change the tokens once at `:root` and say so, rather than scattering one-off exceptions.218219- **Claude Opus 4.8** has strong design instincts with one persistent house default: warm cream/off-white backgrounds (~`#F4F1EA`), serif display type (Georgia, Fraunces, Playfair), italic word-accents, and a terracotta/amber accent. That reads beautifully for editorial, hospitality, and portfolio briefs, and feels wrong for dashboards, dev tools, fintech, healthcare, and enterprise UI. Generic nudges ("don't use cream," "make it clean") just swap in a *different* fixed palette. Two fixes that work: **(1)** specify a concrete alternative direction: palette hexes, type family, radius, motion spec; **(2)** propose 3–4 distinct visual directions first (bg hex / accent hex / typeface + one-line rationale), let the user pick, and build only that one.220- **Anti-slop guard (any model):** avoid the generic-AI tells: Inter/Roboto/Arial/system fonts as display type, purple gradients on white or dark, timid evenly-distributed palettes, cookie-cutter hero layouts. Commit to a cohesive, context-specific direction; dominant colors with sharp accents beat evenly-spread ones.221- **Claude Fable 5** follows brief instructions reliably and can over-comply with prescriptive checklists: over-prescription degrades its output. Apply this skill's *judgment*, not just its letter: name what each animation communicates, pull from one token scale, honor reduced motion. If a recipe fights the product's design language, adapt it deliberately and note the deviation.222223## Reference files224225| File | Read it for |226| --- | --- |227| [motion.md](motion.md) | Duration/easing/spring tokens in depth, enter/exit recipes, interruptibility, origin, stagger, clip-path reveals, 3D transforms, cohesion, when-NOT-to-animate, modern primitives |228| [gestures.md](gestures.md) | Drag/swipe/sheets: 1:1 tracking, velocity handoff, momentum projection, rubber-banding, interruptibility, multi-touch, reduced-motion |229| [interaction-states.md](interaction-states.md) | Hover/active/focus-visible/disabled, loading & skeletons, empty/error states, optimistic UI, menus, tooltips, multimodal feedback |230| [surfaces.md](surfaces.md) | Concentric radius, shadows vs borders & elevation, translucent/glass materials, image outlines, optical alignment, hit areas |231| [typography.md](typography.md) | Text wrapping, smoothing, tabular numbers, hierarchy, measure, line-height, fluid sizing, punctuation and microcopy (UI voice) |232| [layout-and-color.md](layout-and-color.md) | Spacing scale & grouping, alignment, near-black/white, functional color scales, depth/shadow theory |233| [performance.md](performance.md) | Compositor-only animation, `will-change`, FLIP, the tool ladder, perceived speed, 60fps/RAIL |234| [accessibility.md](accessibility.md) | `prefers-reduced-motion` policy, forced-colors, reduced-transparency/contrast, contrast ratios, focus, semantics/ARIA, vestibular safety |235| [vocabulary.md](vocabulary.md) | Reverse glossary: turn a described effect ("the springy popover thing") into the precise term to ask for |236| [PLAN-TEMPLATE.md](PLAN-TEMPLATE.md) | The self-contained plan format [audit mode](#audit-mode-survey-and-plan) writes for a cheaper model to execute |
Run npx skillmds@latest add arjunlohan/finesse in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Finesse It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
arjunlohan (@arjunlohan) published this skill. Their other Agent Skills are listed on their SkillMD profile.