micro-interaction-adder
Micro-Interaction Adder
This skill layers production-grade CSS micro-interactions onto frontend code — new or existing. The goal: every element a user touches should respond, making the interface feel alive, crafted, and trustworthy.
Philosophy
Micro-interactions are not decoration. They:
- Confirm intent — "yes, I saw you click that"
- Communicate state — loading, disabled, selected, error
- Reward attention — subtle delight for the observant
- Establish hierarchy — animated elements feel more important
Rules of thumb:
- Duration:
150–300msfor most UI.400–600msfor entrances. Never over800msfor interactive feedback. - Easing: prefer
cubic-beziercurves overease-in-outdefaults. Use spring-feel curves for scale. - Less is more: one well-executed hover beats five mediocre ones. Pick the 3–5 highest-impact moments.
- Respect
prefers-reduced-motion— always wrap animation-heavy CSS in the appropriate media query.
Step-by-Step Workflow
1. Audit the UI
Before adding anything, scan the code for:
- Interactive elements (buttons, links, inputs, checkboxes, cards, nav items)
- State-bearing elements (loading, error, success, disabled)
- Structural elements that could benefit from entrance animations (modals, dropdowns, toasts)
2. Select Interaction Patterns
Choose from the Pattern Library below. Don't apply everything — pick what fits the component's purpose and the overall aesthetic (see references/pattern-library.md for full recipes).
3. Inject the CSS
- Add transitions to base state, NOT to
:hover/:focusstate. This ensures smooth exit animations too. - Use CSS custom properties (
--transition-fast,--color-focus-ring) for consistency. - Keep interaction CSS co-located with component styles, not in a separate "animations.css" blob.
4. Validate
Check:
- Interactions work on keyboard focus (
:focus-visible), not just mouse hover - Disabled states suppress all transitions (
pointer-events: none; opacity: 0.5) -
prefers-reduced-motionfallback exists for all scale/translate animations - No layout shifts — use
transformandopacityonly (GPU-composited)
Core CSS Variables (inject into :root)
:root {
/* Timing */
--duration-instant: 100ms;
--duration-fast: 150ms;
--duration-base: 200ms;
--duration-slow: 300ms;
--duration-enter: 400ms;
/* Easing */
--ease-out: cubic-bezier(0.0, 0.0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0.0, 0.2, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* slight overshoot */
--ease-snappy: cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* Focus ring */
--color-focus-ring: #3b82f6; /* adjust to brand color */
--focus-ring-width: 3px;
--focus-ring-offset: 2px;
/* Shadows for lift effects */
--shadow-resting: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08);
--shadow-hover: 0 4px 12px rgba(0,0,0,0.15), 0 2px 6px rgba(0,0,0,0.10);
--shadow-active: 0 1px 2px rgba(0,0,0,0.10);
}
Pattern Library (Quick Reference)
Full recipes with variants are in references/pattern-library.md.
| Pattern | Elements | Key Properties |
|---|---|---|
| Hover Scale | Cards, images, avatars | transform: scale(1.03) |
| Button Press | All buttons | scale(0.96) on :active |
| Focus Ring | Inputs, buttons, links | outline + box-shadow on :focus-visible |
| Link Underline Sweep | <a> tags, nav items |
background-size pseudo-element trick |
| Card Lift | Content cards | transform: translateY(-3px) + shadow |
| Icon Wiggle | Icon-only buttons, alerts | @keyframes wiggle on hover |
| Checkbox Spring | Custom checkboxes | scale + opacity on check state |
| Input Focus Expand | Text inputs, textareas | border-color + box-shadow glow |
| Skeleton Loader | Loading states | @keyframes shimmer gradient sweep |
| Fade + Slide Entrance | Modals, dropdowns, toasts | opacity + translateY on mount |
| Ripple Effect | Buttons (Material-style) | Pseudo-element radial expand |
| Toggle Switch | Boolean toggles | Smooth translateX + color shift |
| Progress Bar Fill | Upload, step indicators | width + background transition |
| Tooltip Appear | Tooltips, popovers | opacity + scale(0.95) origin |
Accessibility: prefers-reduced-motion
Always include this block when using scale, translate, or entrance animations:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
For interactions that still need some feedback (e.g., focus rings, color changes), leave
color, background-color, border-color, and opacity transitions intact — only strip
transform and motion-heavy @keyframes.
Framework-Specific Notes
Plain HTML/CSS
- Use the raw CSS patterns from
references/pattern-library.mddirectly. - Add
will-change: transformto frequently animated elements (use sparingly).
React
- For entrance animations on mount/unmount, use CSS classes +
useStatetoggle, or themotionlibrary (framer-motion). Seereferences/react-patterns.md. - Avoid
useEffect+setTimeouthacks for animations — use CSS@keyframeswith a class toggle instead. transition-allis a code smell — always specify the property explicitly.
Tailwind CSS
- Use
transition,duration-{n},ease-{type},hover:scale-{n},active:scale-{n},focus-visible:ring-{n}utilities. - For custom cubic-bezier curves, extend
tailwind.config.jstransitionTimingFunction. - For entrance animations not in core Tailwind, write a thin
@layer utilitiesblock.
Vue / Svelte
- Use
<Transition>(Vue) ortransition:directive (Svelte) for enter/leave. - CSS custom properties approach works identically.
Common Mistakes to Avoid
| ❌ Mistake | ✅ Fix |
|---|---|
transition: all 0.3s |
Specify only changed properties |
Putting transition on :hover only |
Put on base state for smooth exit |
transform + position: fixed combo |
Creates new stacking context — test carefully |
box-shadow transitions on many items |
Expensive — prefer filter: drop-shadow or pseudo-element shadow |
Forgetting focus-visible |
Don't style :focus alone — it fires on click |
animation: spin infinite in lists |
Decimates performance — use will-change sparingly |
Read Next
references/pattern-library.md— Full copy-paste CSS for every pattern in the table above, with light/dark variants and customization notes.references/react-patterns.md— React-specific hooks,framer-motionsnippets, and class-toggle entrance patterns.
More from blunotech-dev/agents
anti-purple-ui
Enforce a strict monochrome UI with a single high-contrast accent color, removing generic tech gradients and “AI-style” palettes. Use when the user wants minimal, anti-AI, or non-generic aesthetics, or says the UI looks too techy or generic.
9harmonize-whitespace
Align all spacing (padding, margins, gaps) to a consistent 4pt/8pt grid. Use when spacing feels off, inconsistent, cramped, or unbalanced, or when the user asks for a spacing scale or alignment fix.
9typographic-hierarchy
Improve typography by adjusting font sizes, weights, spacing, and contrast to create clear visual hierarchy and readability. Use when text feels flat, unstructured, or when the user asks to refine headings, type scale, or overall readability.
6consistent-border-radius
Normalizes rounded corners across a file so buttons, inputs, cards, modals, badges, and all UI elements share the exact same curvature. Use this skill whenever the user mentions inconsistent border radii, wants to unify rounded corners, asks to make UI elements look more cohesive, or says things like "make the corners match", "fix the rounding", "unify border radius", "standardize my rounded corners", or "buttons and cards don't match". Also trigger when the user pastes a CSS/HTML/JSX/TSX file and asks for a design consistency pass, border radius is one of the first things to normalize.
4component-split
Analyze a component and determine when and how to split it based on size, responsibility, and reuse signals, producing a refactored structure with clear boundaries. Use when users share large, mixed-concern, or hard-to-test components, or ask about splitting, refactoring, or improving component architecture.
3any-type-elimination
Find and replace `any` types in TypeScript with precise types, generics, or `unknown` plus narrowing. Use when improving type safety, fixing implicit `any`, removing `any`, or migrating code to strict TypeScript.
3