agentsclimarketplace

A11y dialog

Skill xrnavigation/web-a11y-plugin/skills/a11y-dialog

Web accessibility agent skills — 23 cite-backed skills covering APG widget patterns, audit tooling, ARIA guidance, cognitive accessibility, and more. Works with Claude Code, Codex CLI, and Gemini CLI.

Install
npx -y skills add xrnavigation/web-a11y-plugin --skill a11y-dialog

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 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

Guides accessible modal and non-modal dialog implementation. Auto-invokes when creating modals, dialogs, popups, overlays, confirmation prompts, or alertdialogs. Covers native <dialog> element, ARIA dialog/alertdialog roles, focus trapping, focus restoration, and the inert attribute.

SKILL.md

13.6 KB, as published. Nobody here has run it

Accessible Dialog Implementation

"Blanket statements about where to put focus when opening a modal dialog are wrong, including this one." — Adrian Roselli, 2025


1. Decision: Native <dialog> vs ARIA Dialog

Default to native <dialog>. MDN explicitly recommends: "Use the native <dialog> HTML element when possible." (MDN — ARIA dialog role)

Use native <dialog> when:

  • Building any new dialog — it is the recommended default
  • You need modal behavior (.showModal() gives you focus trapping, backdrop, background inertness, and Escape-to-close for free)
  • You need non-modal/modeless dialogs (.show())

Use ARIA role="dialog" when:

  • You cannot use <dialog> (legacy browser requirements, though increasingly rare)
  • You are enhancing a custom component that cannot be restructured to use <dialog>
  • Shadow DOM edge cases where <dialog> is unavailable

What native <dialog> gives you for free vs what you still own

AspectNative <dialog>ARIA role="dialog"
Focus trappingAutomatic (top layer)Manual JS required
Background inertnessAutomaticManual (inert or aria-hidden)
Backdrop::backdrop pseudo-elementCustom overlay element
Escape to closeBuilt-in (closedby)Manual keydown handler
aria-modalImplicitMust add explicitly
Accessible nameManual (aria-labelledby)Manual (aria-labelledby)

Sources: HTML Spec — The dialog element; MDN — ARIA dialog role


2. Native <dialog> Quick Reference

.showModal() vs .show()

  • .showModal() — modal: top layer, background inert, ::backdrop, defaults to closedby="closerequest" (Escape closes)
  • .show() — non-modal: background remains interactive, no backdrop, defaults to closedby="none"

closedby attribute

ValueBehaviorDefault for
"closerequest"Escape / platform close gesturesModal dialogs
"any"Escape + clicking outside
"none"No automatic closingNon-modal dialogs

Built-in focus management

  1. If any element inside has autofocus, that element receives focus
  2. Otherwise, focus delegates to the dialog's focus delegate
  3. If neither applies, the dialog element itself receives focus

Use autofocus explicitly for predictable focus placement.

Events

  • close — fires after dialog closes
  • toggle / beforetoggle — fires on open/close state changes
  • returnValue — communicates which button closed the dialog

Minimal correct example

<dialog id="confirm" aria-labelledby="confirm-title">
  <h2 id="confirm-title">Confirm deletion</h2>
  <p id="confirm-desc">This action cannot be undone.</p>
  <button autofocus>Cancel</button>
  <button>Delete</button>
</dialog>

<script>
  document.getElementById('confirm').showModal();
</script>

Source: HTML Spec — The dialog element


3. ARIA Dialog Pattern

Use this only when native <dialog> is not viable. You take full ownership of behavior.

Required roles, states, and properties

AttributeRequirementNotes
role="dialog"Required on containerNot needed on <dialog> element
aria-modal="true"Required for modalImplicit with <dialog>.showModal()
aria-labelledbyRequired (preferred)References visible dialog title
aria-labelAlternativeWhen no visible title exists
aria-describedbyRecommendedReferences content describing dialog purpose

Critical rule: Only set aria-modal="true" when your code actually prevents all interaction outside the dialog AND visual styling obscures external content. (APG Dialog Pattern)

Minimal correct ARIA skeleton

<!-- WRONG — role without behavior -->
<div role="dialog" aria-label="Settings">
  <p>Content here</p>
</div>

<!-- RIGHT — role with all required behavior -->
<div role="dialog" aria-modal="true"
     aria-labelledby="dlg-title" aria-describedby="dlg-desc"
     tabindex="-1">
  <h2 id="dlg-title">Settings</h2>
  <p id="dlg-desc">Configure your preferences.</p>
  <!-- focusable content -->
  <button>Save</button>
  <button>Cancel</button>
</div>
<!-- All sibling content must have inert attribute -->

You must implement: focus trapping, Escape to close, focus restoration, background inertness.

Source: APG Dialog (Modal) Pattern


4. Alertdialog: When and How

When to use alertdialog vs dialog

Use alertdialogUse dialog
Action confirmation ("Delete this?")Forms and data entry
Error message confirmationsInformation display
Critical notifications demanding responseMulti-step workflows, settings

The alertdialog role tells assistive technologies to "give alert dialogs special treatment, such as playing a system alert sound." (APG Alert and Message Dialogs)

Required attributes

AttributeRequirement
role="alertdialog"Required on container
aria-labelledby or aria-labelRequired (one of)
aria-describedbyMust reference the alert message element

Example

<dialog role="alertdialog" aria-labelledby="alert-title"
        aria-describedby="alert-msg">
  <h2 id="alert-title">Delete account?</h2>
  <p id="alert-msg">This will permanently delete your account and all data.</p>
  <button autofocus>Cancel</button>
  <button>Delete</button>
</dialog>

Keyboard interaction is identical to modal dialog. Some implementations intentionally block Escape dismissal for alertdialogs.

Source: APG Alert and Message Dialogs


5. Focus Management

Focus placement is context-dependent. There is no single correct answer.

Where focus goes on open

Dialog TypeFocus TargetRationale
Short informational messageClose buttonQuick dismissal; aria-describedby conveys message
Long/interactive contentDialog element or headingUser needs to orient first
Irreversible action (delete, payment)Least destructive option (Cancel)Prevents accidental activation
Brief familiar form (login)First form fieldReduces steps; only if user triggered the dialog
Long/unfamiliar formDialog or heading, NOT form fieldPrevents premature keyboard activation
Legal/financial agreementDo NOT focus "I agree"Prevents accidental acceptance

Do NOT auto-focus text fields in unexpected/unsolicited modals — this is a dark pattern.

Source: Adrian Roselli — Where to Put Focus

Focus restoration on close

Focus returns to the element that had focus before the dialog opened (typically the trigger button). Edge cases:

  • Trigger no longer exists → focus a logically related element
  • Workflow suggests a different target (e.g., newly created row) → focus the contextually appropriate element

Tab cycling (modal only)

  • Tab from last focusable element wraps to first
  • Shift+Tab from first wraps to last
  • The dialog container (tabindex="-1") is excluded from the tab cycle

For detailed rules, see: ${CLAUDE_SKILL_DIR}/references/focus-management-rules.md

Sources: APG Dialog Pattern; a11y-dialog — Focus Considerations


6. The inert Attribute

What it does

The inert attribute makes an element and all descendants non-interactive:

  • No pointer events, no text selection, not editable
  • Not focusable, not exposed to accessibility APIs (screen readers skip entirely)
  • Excluded from find-in-page

Relationship to dialog

  • Native <dialog>.showModal() — background automatically becomes inert. No inert attribute needed.
  • Custom dialog — apply inert to sibling content manually. This replaces the old triple-technique approach.

What inert replaces

Before inert, you needed all three:

  1. aria-hidden="true" on siblings (screen readers)
  2. JavaScript focus trapping (keyboard)
  3. CSS pointer-events: none or overlay (pointer)

inert or native .showModal() replaces all three.

Browser support

Chrome 102+, Firefox 112+, Safari 15.5+. Use it.

Sources: HTML Spec — The inert attribute; MDN — ARIA dialog role


7. Keyboard Interaction

KeyActionNotes
EscapeCloses dialogNative <dialog> handles automatically. For alertdialog, consider blocking Escape.
TabNext focusable element, wraps at endMust be trapped within modal.
Shift+TabPrevious focusable element, wraps at startMust be trapped within modal.
EnterActivates focused controlStandard, no special handling.

Source: APG Dialog (Modal) Pattern


8. Common Mistakes

8.1 Not blocking screen reader virtual cursor from background

<!-- WRONG — CSS overlay does not block screen readers -->
<div class="overlay"></div>
<div role="dialog">...</div>

<!-- RIGHT — use native dialog or inert -->
<dialog>...</dialog>
<!-- or -->
<main inert>...</main>
<div role="dialog" aria-modal="true">...</div>

Screen readers navigate via virtual cursor, which ignores CSS. You must use inert, aria-hidden="true", or native .showModal(). (MDN — ARIA dialog role)

8.2 Always focusing the first interactive element

<!-- WRONG — delete button gets focus on a confirmation dialog -->
<dialog aria-labelledby="t">
  <h2 id="t">Delete account?</h2>
  <button autofocus>Delete permanently</button> <!-- dangerous! -->
  <button>Cancel</button>
</dialog>

<!-- RIGHT — least destructive option gets focus -->
<dialog aria-labelledby="t">
  <h2 id="t">Delete account?</h2>
  <button>Delete permanently</button>
  <button autofocus>Cancel</button> <!-- safe default -->
</dialog>

Focus placement depends on dialog purpose. (Roselli, 2025)

8.3 Missing accessible name

<!-- WRONG — screen reader announces "dialog" with no context -->
<dialog>
  <h2>Settings</h2>
  <p>Configure options.</p>
</dialog>

<!-- RIGHT — dialog has an accessible name -->
<dialog aria-labelledby="settings-title">
  <h2 id="settings-title">Settings</h2>
  <p>Configure options.</p>
</dialog>

Native <dialog> does NOT auto-set aria-labelledby. You must add it. (MDN — ARIA dialog role)

8.4 Not restoring focus on close

When the dialog closes without returning focus, keyboard/screen reader users lose their place in the document. Store the trigger element reference before opening and restore focus on close. (APG Dialog Pattern)

8.5 Using alertdialog for non-urgent content

alertdialog triggers system alert sounds and interrupts screen reader flow. Reserve it for confirmations and critical errors only. (APG Alert and Message Dialogs)

8.6 Adding redundant aria-modal to native <dialog>

When using .showModal(), aria-modal is implicit. Adding it is unnecessary noise. (Roselli, 2020)

For more anti-patterns, see: ${CLAUDE_SKILL_DIR}/references/common-mistakes.md


9. Cross-References

  • aria-decision-framework — when to use ARIA vs native HTML (start here if unsure whether you need role="dialog")
  • focus-management — general focus management patterns beyond dialogs
  • css-a11y — styling considerations for dialogs (backdrop, reduced-motion, forced-colors)

For detailed reference material:

  • ${CLAUDE_SKILL_DIR}/references/focus-management-rules.md — context-dependent focus placement
  • ${CLAUDE_SKILL_DIR}/references/native-dialog-guide.md — complete native <dialog> reference
  • ${CLAUDE_SKILL_DIR}/references/screen-reader-behavior.md — per-AT behavior differences
  • ${CLAUDE_SKILL_DIR}/references/common-mistakes.md — expanded anti-patterns with citations
  • ${CLAUDE_SKILL_DIR}/references/sources.yaml — provenance for all cited sources

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.