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-broken-focus-trap.md

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

Modal without working focus trap

When a modal opens, Tab and Shift+Tab must cycle inside it and Esc must close it. Without a focus trap, keyboard users tab into the page behind the modal, lose context, and may not realise it is open. Hand-rolled traps almost always miss edge cases (iframes, contenteditable, dynamically-added focusables). Use a primitive that gets it right: Radix UI, react-aria, or focus-trap-react.

What goes wrong

A custom <div role="dialog"> opens. The user presses Tab; focus moves to a body link below the modal. They keep tabbing and navigate the page underneath, hidden by the scrim. Total disorientation. Screen-reader users fare worse: VoiceOver navigates the entire DOM, ignoring the modal.

Detection

Surfaces: modal, sheet, drawer, popover, command-palette.

Static signals:

  1. rg 'role="dialog"|role="alertdialog"' --type=ts -l: find all dialog markup.
  2. For each file, confirm one of these imports/usages:
    • @radix-ui/react-dialog (built-in trap).
    • react-aria / react-aria-components (built-in trap).
    • focus-trap-react (<FocusTrap>).
    • Headless UI <Dialog>.
  3. Flag any role="dialog" markup with no trap library import.
  4. Bonus: confirm Esc closes the modal (onKeyDown for Escape OR primitive's built-in).

Concrete commands:

# Hand-rolled dialogs
rg 'role="(dialog|alertdialog)"' --type=ts -l | while read f; do
  rg -q '@radix-ui/react-dialog|react-aria|focus-trap-react|@headlessui/react' "$f" \
    || echo "$f: dialog without trap library"
done

# Components named *Modal*/*Dialog* without primitive
rg -l --type=ts '(Modal|Dialog|Sheet|Drawer|Popover)\b' src/ | while read f; do
  rg -q '@radix-ui|react-aria|@headlessui|focus-trap' "$f" \
    || echo "$f: custom modal without primitive"
done

False-positive guards:

  • Skip non-modal dialogs (role="dialog" with aria-modal="false": rare, but valid).
  • Skip components imported from a known wrapper that already uses Radix/react-aria internally.
  • Skip files annotated // ui-audit-ignore:focus-broken-focus-trap.

Fix

Use Radix UI Dialog (or react-aria's <Modal>). Both ship with focus trap, restoration, Esc handling, scroll lock, and aria-modal="true".

// before: hand-rolled, no trap, no Esc
function MyModal({ open, onClose, children }) {
  if (!open) return null;
  return (
    <div role="dialog" aria-modal="true">
      <button onClick={onClose}>Close</button>
      {children}
    </div>
  );
}

// after: Radix Dialog
import * as Dialog from '@radix-ui/react-dialog';

export function ConfirmDialog({ children, trigger }) {
  return (
    <Dialog.Root>
      <Dialog.Trigger asChild>{trigger}</Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Overlay className="fixed inset-0 bg-black/50" />
        <Dialog.Content className="fixed inset-0 m-auto h-fit w-fit p-6">
          <Dialog.Title>Confirm</Dialog.Title>
          <Dialog.Description>Are you sure?</Dialog.Description>
          {children}
          <Dialog.Close>Cancel</Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

Docs:

Default tier and overrides

Defaults to: release-blocker

Surface overrides:

Surface Tier
Sign-in / Checkout modal release-blocker
Confirm-destruction dialog release-blocker
Marketing newsletter modal fix-this-sprint
Internal admin fix-this-sprint

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

  • axe-core / jsx-a11y for missing aria-labelledby/aria-label on the dialog.
  • Lighthouse a11y audits for the WCAG-criteria coverage.
  • Manual VoiceOver / NVDA pass for screen-reader correctness.

Suppression

{/* ui-audit-ignore:focus-broken-focus-trap, non-modal popover, trap intentionally off */}

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 4 hours ago.

Activeupdated last week

README badge

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