Micro-interactions skill for Codex

Feedback that answers did-it-work in 100 ms. Codex defines each micro-interaction by trigger, rule, feedback and reset, fills in a full state matrix for every control, and implements feedback that appears within 100 ms using transform and opacity only. It gates hover to hover-capable pointers, rolls back failed optimistic updates, announces async results to screen readers and keeps feedback visible under reduced motion.

  • Design & UX
  • $micro-interactions
  • 205 lines
  • 2 files
  • ~2,000 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- micro-interactions

Installs to ~/.agents/skills/micro-interactions/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $micro-interactions, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs and implements purposeful micro-interactions - full state matrices for controls (hover, focus-visible, pressed, loading, success, error), feedback within 100 ms, optimistic updates with rollback, toggles, copy-to-clipboard, inline confirmations, entry and exit transitions with @starting-style, hover gated to hover-capable pointers, and prefers-reduced-motion fallbacks. Use when buttons or controls feel dead or laggy, adding hover and press states, building toggles, likes, copy buttons or inline saves, or when asked to make an interface feel more responsive or polished.

Use it when

  • Controls lack hover, pressed, focus or loading feedback
  • Actions feel slow even when the server is fast
  • Building toggles, copy buttons, inline saves or undo patterns
  • Popovers and menus appear or vanish abruptly

Use something else when

How to check the result

Every control shows distinct hover, focus-visible, pressed and loading states, feedback appears within 100 ms, and with reduced motion emulated the feedback is still visible without movement.

The mistake it prevents

Leaving hover styles ungated, so on touch screens the last tapped button stays highlighted as if still hovered.

~/.agents/skills/micro-interactions/SKILL.md

--- name: micro-interactions description: Designs and implements purposeful micro-interactions - full state matrices for controls (hover, focus-visible, pressed, loading, success, error),… license: MIT ---

A micro-interaction earns its place when it answers a question the user has at that moment: “Did it register?”, “Is it working?”, “Did it succeed?”, “What changed?”. It must appear within 100 ms of the input, finish quickly enough never to block the next action, and degrade to an instant state change under reduced motion. Animation that answers nothing is decoration and costs attention.

Anatomy

For each interaction, define four parts before writing CSS:

PartQuestionExample (copy button)
TriggerWhat starts it?Click or Enter/Space on the button
RuleWhat happens?Text goes to clipboard
FeedbackHow does the user know?Icon swaps to a check, label “Copied”, announced to screen readers
Loop / resetWhat happens after?Reverts after 2 s; repeated clicks restart the timer

State matrix

Every interactive control needs these states designed, not left to browser defaults:

StateSelector / attributeTypical treatment
RestdefaultBase token colors
Hover@media (hover: hover) { :hover }Background shift of about 4–8% lightness
Focus:focus-visible2 px outline, 2 px offset, 3:1 contrast against surroundings
Pressed:active, aria-pressed="true" for togglesDarker shift plus scale(.97) or 1 px translate
Disabled:disabled, aria-disabled="true"Reduced contrast, cursor: not-allowed, still readable
Loadingaria-busy="true"Spinner or progress replaces icon; label keeps width
Success / errordata attribute or classBrief icon and color change, then reset or persist
Selected / currentaria-selected, aria-currentPersistent, not just hover-like
.btn {
  transition: background-color 120ms ease-out, transform 120ms ease-out, box-shadow 120ms ease-out;
}
@media (hover: hover) {
  .btn:hover { background: oklch(from var(--btn-bg) calc(l - 0.05) c h); }
}
.btn:active { transform: scale(.97); transition-duration: 60ms; }
.btn:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
.btn[aria-busy="true"] { pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .btn, .btn:active { transition: none; transform: none; }
}

(hover: hover) stops sticky hover states on touch screens, where the last tapped button keeps its hover color.

Timing

InteractionDurationEasing
Hover, press, focus color80–150 msease-out
Toggle, checkbox, small icon swap150–200 msease-out or a gentle spring
Dropdown, popover, tooltip enter150–220 mscubic-bezier(.2,.8,.2,1)
Exit of the same100–150 ms (faster than enter)ease-in
Toast, inline banner200–300 msdecelerate
Success hold before reset1.5–2.5 snone

Respond visually within 100 ms; keep Interaction to Next Paint at 200 ms or less. If the real work takes longer, show progress immediately and finish later. For choreography of larger transitions, see motion-design-principles.

Patterns

Optimistic toggle with rollback

function LikeButton({ id, initial }: { id: string; initial: boolean }) {
  const [liked, setLiked] = useState(initial);
  const [error, setError] = useState<string | null>(null);

  async function toggle() {
    const next = !liked;
    setLiked(next);                       // instant feedback
    setError(null);
    try {
      await api.setLike(id, next);
    } catch {
      setLiked(!next);                    // roll back
      setError("Couldn't save. Try again.");
    }
  }

  return (
    <>
      <button type="button" aria-pressed={liked} onClick={toggle} className="like">
        <HeartIcon aria-hidden="true" /> {liked ? "Liked" : "Like"}
      </button>
      <span role="status" className="sr-only">{error ?? ""}</span>
    </>
  );
}

Use optimistic updates only when the action almost always succeeds and is reversible. Payments, deletes and sends wait for confirmation. React 19’s useOptimistic covers the same pattern inside actions.

Copy to clipboard

button.addEventListener("click", async () => {
  try {
    await navigator.clipboard.writeText(code.textContent);
    button.dataset.state = "copied";
    status.textContent = "Copied to clipboard";
  } catch {
    button.dataset.state = "error";
    status.textContent = "Copy failed. Select the text and copy manually.";
  }
  clearTimeout(button._t);
  button._t = setTimeout(() => { button.dataset.state = ""; status.textContent = ""; }, 2000);
});

status is an element with role="status" that exists before the click, so screen readers announce the change. The Clipboard API requires a secure context (HTTPS or localhost).

Entry and exit without JavaScript timers

.popover {
  transition: opacity 180ms ease-out, translate 180ms ease-out, display 180ms allow-discrete, overlay 180ms allow-discrete;
  opacity: 1; translate: 0 0;
}
.popover:not(:popover-open) { opacity: 0; translate: 0 -4px; }
@starting-style {
  .popover:popover-open { opacity: 0; translate: 0 -4px; }
}

@starting-style defines the “before” state for elements entering from display:none, and allow-discrete lets display and overlay flip at the end of the exit. Both are supported in current evergreen browsers; without them, the popover simply appears instantly, which is an acceptable fallback.

Inline save feedback

For autosaving fields, show a small status next to the field: “Saving…” during the request, “Saved” with a check for 2 s, or a persistent error with retry. Never a toast for every keystroke save.

Destructive actions

Prefer undo over confirmation for reversible deletes: remove immediately, show “Deleted. Undo” for 5–8 s, commit after. Use a confirmation dialog only for irreversible actions, and name the object in the button (“Delete 3 invoices”).

Rules

  • Animate transform and opacity; avoid animating width, height, top, left, which trigger layout on every frame.
  • Keep labels from shifting width when states change (reserve space or swap within a fixed-width box).
  • One feedback channel per event is usually enough; color + icon + motion + toast for one click is noise.
  • Under prefers-reduced-motion: reduce, replace movement with instant state or opacity fades; do not remove the feedback itself.
  • Feedback must not rely on color alone; pair with icon or text.
  • Focus never moves unexpectedly as a side effect of a micro-interaction.

Workflow

- [ ] 1. List controls and the user question each interaction answers
- [ ] 2. Fill the state matrix per control (design tokens for every state)
- [ ] 3. Implement with transitions on transform/opacity/color only
- [ ] 4. Add reduced-motion and hover-capability guards
- [ ] 5. Add screen-reader announcements for async results
- [ ] 6. Verify timing, INP and accessibility

Verification

  • Slow animations in Chrome DevTools (Animations panel, 10% speed) to inspect easing and overlaps.
  • Emulate reduced motion (DevTools Rendering panel, or Playwright page.emulateMedia({ reducedMotion: "reduce" })): feedback still visible, no movement.
  • Keyboard-only pass: every state reachable and visible, including focus on pressed and loading buttons.
  • Throttle network to Slow 4G: loading states appear within 100 ms; rollback works when the request fails (block the URL in DevTools).
  • Check INP in the Performance panel for the busiest interaction; target 200 ms or less.

Output contract

**Interactions:** <control>: trigger / rule / feedback / reset
**States implemented:** hover, focus-visible, pressed, disabled, loading, success, error
**Timing tokens:** fast 120 ms, base 180 ms, slow 280 ms; easing <values>
**Reduced motion:** <behavior>  **Announcements:** <role=status regions>
**Checks:** keyboard ok, reduced motion ok, failure rollback ok, INP <n> ms

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Micro-interactions"
  short_description: "Feedback that answers did-it-work in 100 ms"
  default_prompt: "$micro-interactions Our buttons feel dead, add proper hover, press, focus and loading states"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem