All skills
mblode avatar

/ui-design

@e7db33d
by Matthew Blodemblode/agent-skills136 stars
12

Designs and builds React/Next/Tailwind UI and audits visual and interaction defects. Use when asked to "build a landing page", "extract our design system", "add dark mode", "make this responsive", "remove UI slop", or "audit this component". For product decisions use product-design; for browser measurements use ui-verification; for motion use ui-animation.

Use this Skill: https://skilld.dev/gh/mblode/agent-skills/ui-design

This session only. Nothing lands on disk.

rulesfocus-not-restored.md

≈1.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Focus not restored after modal/sheet/popover close

When a dialog closes, focus must return to the element that opened it (the trigger). If it does not, keyboard and screen-reader users land on <body> and must tab from the top to recover their place. One of the most common accessibility bugs, and invisible to mouse users. Radix and react-aria handle it automatically; hand-rolled dialogs almost never do.

What goes wrong

User opens "Edit profile" via keyboard, submits, and closes. Focus drops to <body>; Tab now jumps to the page header, three sections from where they were working, with no way back without scanning the whole page.

Detection

Surfaces: modal, sheet, drawer, popover, command-palette, any "Forgot password?" trigger.

Static signals:

  1. rg 'role="(dialog|alertdialog)"|<Dialog|<Sheet|<Popover' --type=ts -l.
  2. For each, confirm focus-restoration evidence:
    • Radix onCloseAutoFocus handler OR not overriding it (Radix default restores).
    • A triggerRef passed and triggerRef.current?.focus() called on close.
    • react-aria's <DialogTrigger> (built-in restoration).
    • focus-trap-react with returnFocusOnDeactivate (defaults to true).
  3. Flag dialogs that override onCloseAutoFocus with e.preventDefault() without focusing another element.

Concrete commands:

# Dialogs missing focus-restoration evidence
rg 'role="dialog"|<Dialog\b|<Sheet\b' --type=ts -l | while read f; do
  rg -q 'onCloseAutoFocus|triggerRef|finalFocus|returnFocusOnDeactivate' "$f" \
    || echo "$f: dialog with no focus restoration"
done

# Cases where onCloseAutoFocus is preventDefault'd
rg -B 1 -A 3 'onCloseAutoFocus' --type=ts | rg 'preventDefault'

False-positive guards:

  • Skip if Radix is used and onCloseAutoFocus is not provided (default behaviour restores focus).
  • Skip if the dialog closes by navigating to a new route (focus management is the new page's responsibility).
  • Skip files annotated // ui-audit-ignore:focus-not-restored.

Fix

Patterns:

A. Use Radix (no extra code needed):

// Radix handles restoration automatically: leave onCloseAutoFocus alone.
<Dialog.Root open={open} onOpenChange={setOpen}>
  <Dialog.Trigger asChild>
    <button>Edit profile</button>
  </Dialog.Trigger>
  <Dialog.Content>...</Dialog.Content>
</Dialog.Root>

B. Hand-rolled with triggerRef:

function EditProfile() {
  const triggerRef = useRef<HTMLButtonElement>(null);
  const [open, setOpen] = useState(false);

  const close = () => {
    setOpen(false);
    // Defer to allow unmount before focus
    queueMicrotask(() => triggerRef.current?.focus());
  };

  return (
    <>
      <button ref={triggerRef} onClick={() => setOpen(true)}>
        Edit profile
      </button>
      {open && <MyDialog onClose={close} />}
    </>
  );
}

C. Override Radix carefully (e.g. focus a confirm result, not the trigger):

<Dialog.Content
  onCloseAutoFocus={(e) => {
    e.preventDefault();
    successBannerRef.current?.focus(); // explicit alternative target
  }}
>

Docs:

Default tier and overrides

Defaults to: release-blocker

Surface overrides:

Surface Tier
Sign-in (Forgot password modal) release-blocker
Checkout (address modal) release-blocker
Confirm-delete dialog release-blocker
Marketing newsletter fix-this-sprint
Internal admin fix-this-sprint

Defer-to (when this is another tool's job)

  • axe-core: WCAG 2.4.3 (Focus Order) checks at runtime.
  • Manual keyboard pass; automated tooling can't always verify "focus returned to the right place."
  • Storybook a11y addon for component-level checks.

Suppression

{/* ui-audit-ignore:focus-not-restored, close action navigates to new route, parent owns focus */}

Source: SKILL.md on GitHub

No alerts8d5 checks · Risk SAFE
  • Gen Agent Trust Hub8d

    A comprehensive UI design and auditing skill that uses standard developer tools and trusted services like Tailwind CSS and placeholder asset APIs to build and verify React/Next.js interfaces. It includes explicit guardrails to prevent instruction following from analyzed project files.

  • Socket8d

    No alerts

  • Snyk8d

    Risk: LOW · No issues

  • Runlayer6mo

    4/5 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at e7db33d. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 hours ago.

Activeupdated last week

README badge

README badge for mblode/agent-skills/ui-design