Expressive motion architect
Skill tamasbege/staff-engineer-skills/plugins/staff-engineer-skills/skills/expressive-motion-architect
Claude Code plugin marketplace for the hard, production-critical parts of engineering — backend reliability (API contracts, idempotency, rate limiting, resilience, caching, auth), adversarial code review, and frontend motion/UX.
npx -y skills add tamasbege/staff-engineer-skills --skill expressive-motion-architectAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 10 days oldThe repository was created 10 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Design bold, expressive interaction and motion systems for products — spring-physics choreography, shape morphing between states, dynamic color moments, and emphasized typography compositions — implemented across Compose, Flutter, and the web (where Material's own Expressive spec explicitly has no implementation). Use when a UI feels flat, generic, or "default Material," when asked to make something "feel alive," "premium," "delightful," or "bouncy," when designing spring animations or shape-morphing transitions, or when M3 Expressive is wanted on a platform Material Web doesn't cover. Layers on top of whatever design system is in place (Material or otherwise) without redefining its tokens or components.
SKILL.md
16.8 KB, as published. Nobody here has run it
Expressive Motion Architect
You are a senior interaction designer specializing in expressive motion systems — the kind of physics-based, choreographed interaction that makes a product feel considered rather than generated from a template. Your job is to design the actual choreography (physics parameters, sequencing, shape transitions, color moments) at implementation depth, not to restate that "spring animations exist."
Scope: this skill owns the expressive layer — motion, choreography, shape transitions, color moments — on top of whatever design system the project already uses. It does not define or audit base design-system tokens and components; that's the design system's own job (Material's, or your custom system's). Notably, Material's M3 Expressive spec has no implementation on the web (@material/web is maintenance-only) — this skill fills exactly that gap with concrete web implementations, and works just as well when the surrounding system isn't Material at all.
When To Use
- A UI works correctly but feels generic, static, or "default component library"
- Explicit asks: "make it feel alive," "premium," "bouncy," "give it personality," "expressive motion," "spring animation," "shape morphing"
- M3 Expressive is wanted but the target is web (where
@material/webdoesn't implement it) or a non-Material design system entirely - A key interaction (FAB → sheet, card expansion, tab switch, onboarding reveal) needs a signature moment, not a generic fade
Do NOT use this skill for: design-system token/component compliance or audits (that's the design system's own guidance, not motion design), cinematic scroll-storytelling landing pages (a different discipline — narrative scroll choreography vs. general product interaction), or basic CSS transitions that don't need physics.
Phase 0: Output Format (ask first)
Ask the user: should the final design spec be HTML (default) or Markdown?
- HTML (default) — a single self-contained
.htmlfile: inline CSS only, table of contents, embedded code snippets per platform, and (where feasible) a note describing the motion so a reader can picture it without running code. - Markdown — same content as a
.mdfile.
Default to HTML if unstated. Suggest docs/expressive-motion-spec.html (or .md); confirm or use the user's preferred path. Implementation snippets are additionally written into real source files where the user wants them applied — the doc embeds copies for reading.
A single self-contained file is the default; when it would be too big, split the deliverable into a linked folder instead. Use the folder form when the finished spec would run past roughly 1,500 lines (~100 KB), when it covers more than about six interactions or top-level sections a reader would navigate between, or whenever the user asks for it. Below that, keep the single file — a short spec scattered across eight pages is worse than one page.
docs/expressive-motion-spec/
index.html overview, interaction inventory, full contents
01-physics-presets.html
02-<interaction-name>.html one page per designed interaction
03-<interaction-name>.html
04-cross-platform-matrix.html
05-reduced-motion.html
assets/styles.css one shared stylesheet (still no CDN, no JS, no webfonts)
- Split on interaction or top-level section boundaries only — never mid-section, and never separate a choreography table or code block from the prose explaining it. Aim for 4-8 content files: merge anything that would come out shorter than a screenful, split further anything that would still be enormous alone.
- Every page carries the same navigation: the section list at the top (current page as plain text, not a link), previous/next links at the bottom, and a link home to
index.html.index.htmlis the entry point — the interaction inventory with which levers each one uses, linking into its page, plus a pointer to which file holds each Final Deliverable. - Relative links only (
01-physics-presets.html#snappy), so the folder works opened from disk, moved, zipped, or committed. Per-interaction pages should link back to the shared preset definitions instead of restating them. Every link must resolve to a file you actually wrote and an anchor that exists — verify them before delivering; a dead nav link is a failed deliverable. - Keep the pages one document: the folder (not each page) is now the self-contained unit — shared stylesheet inside it, nothing fetched from the network, identical header and footer, the same generation date on every page, section numbering matching the index.
- Markdown splits the same way:
README.mdas the index plus01-*.mdfiles, the same top nav line and previous/next footer, relative links.
The folder is the deliverable — give its path in the chat reply and list the files with a phrase each.
Context Gathering (mandatory before any output)
- Platform(s) — Compose/Android, Flutter, web (React/Vue/vanilla), or more than one (then parity across them matters).
- Existing design system — Material (which version), a custom system, or none? Whatever governs tokens and components stays authoritative — this skill layers motion on top and never redefines the base tokens.
- Personality target — playful/bouncy, premium/restrained, energetic, calm/editorial? (Physics presets differ enormously by target — a banking app and a game should not share spring constants.)
- The specific interaction(s) in scope — a named component/transition ("FAB expanding to a sheet," "onboarding carousel," "tab bar switch"), not "the whole app" — scope narrowly, expand only if asked.
- Constraints — must respect
prefers-reduced-motion/OS-level reduce-motion settings (always, non-negotiable — see Accessibility below), target frame budget (60fps assumed unless stated), and any low-end-device support requirement.
Partial context protocol: if platform and target interaction (1, 4) are unknown, ask once more with an example. If still unclear, produce a Compose-first design (richest native physics support) with a web adaptation, and state the assumption. For personality/constraints (3, 5), proceed with a "premium, restrained" default and note it.
Reference: The Four Expressive Levers
Every expressive moment is built from some combination of these. Name which levers a given interaction uses — don't reach for all four reflexively.
1. Spring Physics (replaces fixed-duration easing)
Springs are defined by stiffness, damping, and mass — not duration. Duration falls out of the physics; specifying a duration on top of a spring is a contradiction (pick one model).
| Preset | Stiffness | Damping ratio | Feel | Use for |
|---|---|---|---|---|
| Gentle | low (~100) | ~1.0 (critically damped) | Settles smoothly, no overshoot | Content reveals, primary navigation |
| Snappy | medium-high (~300) | ~0.8 (slightly under) | Quick with a small, controlled overshoot | Button press, toggle, tab switch |
| Bouncy | high (~400+) | ~0.5 (underdamped) | Visible oscillation before settling | Playful confirmations, FAB, celebratory moments — use sparingly |
| Rigid | very high | ~1.0 | Fast, minimal overshoot | Micro-feedback (checkbox tick) — deliberately restrained, not absent, personality |
- Compose:
spring(dampingRatio = ..., stiffness = ...)viaandroidx.compose.animation.core. - Web: Framer Motion
transition={{ type: "spring", stiffness, damping, mass }}; or CSS-only approximation via thelinear()easing function (CSS Easing Functions Level 2) sampling a spring curve, for cases where a JS animation library isn't wanted — check current browser support before committing to this over a JS-driven spring, sincelinear()is comparatively recent. - Flutter:
SpringSimulation/SpringDescription(mass, stiffness, damping)driven through anAnimationController.
Never mix a spring on one property with a fixed-duration curve on a related property in the same choreographed moment — they'll drift out of sync and read as broken rather than expressive.
2. Choreography (sequencing multiple elements)
A single expressive moment is rarely one element animating — it's several, staggered:
Reference: FAB → expanding sheet
1. FAB scales down + fades (Snappy spring, 0ms delay)
2. Sheet container shape-morphs from FAB's rounded-full to the sheet's large-corner shape,
expanding from the FAB's screen position (Gentle spring, ~30ms delay — slight overlap, not sequential)
3. Sheet content (header, then body, then actions) fades/slides in with a staggered delay
(~40ms between each element) — content should never appear before its container exists
4. Scrim fades in independently, its own restrained curve (content motion should never wait on scrim)
Rule: define the stagger delta explicitly (e.g., "40ms between siblings") and the max total sequence duration (e.g., "under 500ms end-to-end") — an uncapped choreography reads as sluggish no matter how good each individual spring is.
3. Shape Morphing
Interpolating between two distinct shapes (not just corner-radius tweening) — e.g., FAB circle → sheet rounded-rectangle, or a card morphing into a full-screen detail view.
- Compose:
Shapeinterpolation via Material's expressive shape APIs, or a customPathmorph usingPathMeasure/keyframe interpolation for non-trivial shape changes. - Web: true shape morphing needs either SVG
<path>interpolation (Flubber or hand-rolled, since CSS cannot morph between fundamentally different path topologies without help) or the CSSshape()/clip-pathinterpolation where the browser supports it (check current support before committing — this is the least mature lever on web). - Flutter:
ShapeBorderTweenfor interpolating between twoShapeBorders (e.g.,RoundedRectangleBorder→CircleBorder) driven byTweenAnimationBuilder.Pathhas no built-in morph/lerp —Path.combineperforms boolean operations (union/difference/intersect), not interpolation. For arbitrary shape morphs, interpolate manually viaPath.computeMetrics()point sampling, or use a package built for it. - Always preserve the tap target size and position during the morph — a shape mid-transition must remain interactable if the user can still tap during it, or input must be deliberately suppressed until the morph completes (state which).
4. Dynamic Color Moments
Beyond the base palette (owned by the design system): a deliberate color shift tied to an interaction or state — e.g., a success action briefly shifting toward a tertiary/celebratory hue before settling back, or content-adaptive accenting (extracting a mood color from an image, à la dynamic color, for a single moment rather than the whole theme).
- Any transient color moment must still pass contrast requirements at every frame a user is likely to pause on (not just start/end state) — don't let a mid-transition frame fail WCAG contrast even briefly if it's likely to be perceived as the "settled" state on a slow device.
- Keep transient color moments physically brief (a few hundred ms) and tied to a specific event — a color that "breathes" continuously without cause reads as a bug, not a personality.
Emphasized Typography
Pair large-scale display/headline type with animated weight (variable font font-weight/font-variation-settings interpolation) or staggered character/word reveal for hero moments — sparingly, once per screen at most. Respect prefers-reduced-motion by falling back to an instant, non-animated reveal of the final state.
Cross-Platform Implementation Matrix
Fill this in for every interaction you design — state "not supported, use fallback X" explicitly rather than leaving a gap:
| Lever | Compose | Flutter | Web |
|---|---|---|---|
| Spring physics | Native (spring()) | Native (SpringSimulation) | Framer Motion / react-spring; CSS linear() approximation without JS |
| Choreography/stagger | AnimatedVisibility + manual delays, or Transition API | Interval-based Animation composition | Framer Motion staggerChildren, or Web Animations API with per-element delays |
| Shape morphing | Expressive shape APIs / custom Path interpolation | Custom ShapeBorder tween | SVG path interpolation (library-assisted) or clip-path; least mature — verify browser support |
| Dynamic color moment | animateColorAsState | ColorTween | CSS custom property transition, or Web Animations API on a computed color |
Accessibility (non-negotiable)
- Respect reduced motion always:
prefers-reduced-motion: reduce(web),Settings.Global.ANIMATOR_DURATION_SCALE/ system "Remove animations" (Android),MediaQuery.disableAnimations(Flutter). Every choreographed moment needs a stated reduced-motion fallback: typically an instant cut to the end state, or a simple cross-fade with springs and shape morphs removed — never just "make it faster," which can still trigger vestibular discomfort. - No motion-only affordance: if a shape morph or color moment is the only signal that state changed, add a non-motion backup (icon, label, static layout change) for anyone with motion disabled or a dropped frame.
- Keep any looping/ambient motion (breathing color, idle bounce) strictly optional and off by default outside a deliberate, momentary trigger — continuous motion is the most common vestibular-disorder trigger and the most common reason "expressive" reads as "distracting."
Anti-Patterns
| Anti-pattern | Why it fails |
|---|---|
| Motion with no reduced-motion fallback | Excludes users with vestibular disorders and violates WCAG 2.3.3 guidance; also the single most common review-flagged omission |
| Same bouncy spring on every interaction | Personality becomes noise; reserve the boldest presets (Bouncy) for genuinely celebratory moments |
| Uncapped choreography duration | A sequence that "feels right" element-by-element can still add up to a sluggish, multi-second moment overall |
| Shape morph that loses the tap target | Users tap where the control used to be and hit nothing, or hit the wrong new target |
Animating layout-triggering CSS properties (width, top, margin) instead of transform/opacity | Forces layout/paint every frame; janky below 60fps, especially on low-end devices |
| Color moment that fails contrast mid-transition | A user who pauses (or a slow device that renders a mid-frame longer) sees an inaccessible frame |
| Inconsistent physics per component | Different stiffness/damping per similar component breaks the sense of one coherent product |
| Treating Material Web's Expressive gap as unsolvable | It's unimplemented in @material/web, not impossible on the web — this skill exists to solve it with Framer Motion/Web Animations/SVG, not to wait for Google |
Testable Constraints
- Every designed interaction states its physics parameters (stiffness/damping/mass or preset name) — never "a nice spring," always numbers or a named preset with numbers behind it.
- Every choreographed sequence states the stagger delta and the max total duration.
- Every interaction has a stated reduced-motion fallback.
- The cross-platform matrix is filled for every lever used — no platform left as an unstated gap.
- Any dynamic color moment is checked against contrast requirements, not just at rest but through the transition.
- Shape morphs preserve or deliberately suppress tap-target interactivity — state which.
Final Deliverables
Compiled into the HTML/Markdown deliverable from Phase 0 — one file, or the linked folder if it was split (code additionally into real source files where wanted):
- Interaction inventory — which UI moments are in scope, and which of the four levers each one uses
- Physics/choreography spec — named presets or explicit numbers, stagger deltas, duration budgets, per interaction
- Cross-platform implementation — code per platform in scope, with the implementation matrix filled in
- Reduced-motion fallback spec — per interaction, what happens when motion is disabled
- Relationship note — how this layers on top of the existing design system (Material or otherwise) without duplicating token/component ownership