CtrlK
BlogDocsLog inGet started
Tessl Logo

workspace-brfis/create-accessible-modals

Build accessible dialogs/modals in the frontend

72

Quality

90%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files
name:
create-accessible-modals
description:
Create or improve accessible modal dialogs in frontend applications, including semantics, focus management, keyboard behavior, inert background content, form errors, and verification. Prefer native HTML dialog when it fits; use this for implementation and review, not for generic visual styling.

create-accessible-modals

Create a modal that is understandable and operable with a keyboard and assistive technology. Keep the application’s existing framework and visual language unless the user asks for broader changes.

For the rationale and source links behind these requirements, read references/research.md.

Choose the implementation

  • Prefer the native HTML <dialog> with showModal() for a true modal. It provides the modal behaviors required below — focus, inertness, containment, Escape, and focus restoration — without custom code.
  • Use an existing, well-tested dialog primitive from the project when one exists, but verify that it actually provides the behaviors below rather than assuming its name implies accessibility.
  • Build a custom role="dialog" implementation only when native dialog or the existing primitive cannot meet the product or browser constraints. A custom dialog must reproduce modal behavior; adding ARIA attributes alone is insufficient.
  • Use role="alertdialog" only for brief, urgent messages that interrupt the user and require an immediate response, such as an irreversible confirmation.

Minimal native example

<dialog id="confirm" aria-labelledby="confirm-title">
  <form method="dialog">
    <h2 id="confirm-title">Delete this item?</h2>
    <p>This cannot be undone.</p>
    <button type="submit" value="cancel">Cancel</button>
    <button type="submit" value="delete">Delete</button>
  </form>
</dialog>

<script>
  const dlg = document.getElementById('confirm');
  // Open as a true modal: focus moves in, background becomes inert, Escape closes.
  dlg.showModal();
</script>

A visible close control is provided by the Cancel button (a method="dialog" form submits and closes the dialog). Apply the semantics, focus, and verification sections below on top of this baseline.

Required dialog semantics

  • Give the dialog an accessible name. Prefer a visible heading referenced with aria-labelledby; otherwise use a meaningful aria-label. Do not provide both unless they serve distinct, intentional purposes.
  • Add aria-describedby only for a short, simple description. Omit it when the content contains paragraphs, lists, tables, or other structure that would be hard to hear as one concatenated announcement.
  • For a native modal opened with showModal(), do not add redundant ARIA or tabindex attributes to the <dialog> element. In particular, do not put tabindex on a native <dialog>.
  • For a custom modal, use role="dialog" and aria-modal="true" only when the background is genuinely inert for every user: it must be visually obscured and impossible to reach by pointer or keyboard. Keep the dialog outside any ancestor hidden from assistive technology.
  • Use real headings, labels, buttons, links, and form controls inside the dialog. Every control must have an accessible name.

Focus and keyboard behavior

  • When opening, move focus to a sensible element inside the dialog. Choose based on content: the first useful control for a short form, a static heading or introductory element with tabindex="-1" for long/structured content, or the least destructive action for an irreversible decision.
  • Keep Tab and Shift+Tab within a custom modal. Native modal dialogs handle this containment; do not add a second competing focus trap without evidence it is needed.
  • Support Escape to close a modal, unless the product explicitly requires a different, documented behavior. Ensure only the topmost dialog closes when dialogs can stack.
  • Provide a clearly named visible close/cancel control. Do not make backdrop-click the only dismissal mechanism. If backdrop click is supported, do not let clicks inside the dialog dismiss it accidentally.
  • On close, restore focus to the opener when it still exists. If it was removed, choose the next logical workflow target and test that path.
  • Avoid positive tabindex values. Preserve a logical DOM and focus order.

Forms and interaction details

  • Associate every input with a visible <label> and connect validation text with aria-describedby (and aria-errormessage only when the project’s support strategy justifies it). Set aria-invalid="true" while an error is present.
  • Make errors visible near the relevant field, specific enough to fix, and announced through normal semantic associations; do not rely on a generic alert alone.
  • Decide whether submitting valid data closes the dialog, and return focus to a meaningful result or trigger when the workflow requires it.
  • Make the modal usable at narrow viewport widths: allow the dialog content to scroll without trapping the close control, preserve visible focus indicators, and avoid clipping essential actions.
  • Do not close a dialog on an accidental outside click when it would discard entered data or interrupt a critical task without warning.

Verification before finishing

Inspect the implementation and, when the project supports it, add or update automated tests for the primary workflow. Verify:

  1. The opener is keyboard operable and has an accessible name.
  2. Opening moves focus inside the dialog and exposes its intended name.
  3. Tab/Shift+Tab cannot reach the page behind a custom modal; the background is inert for native or custom implementations.
  4. Escape and the visible close/cancel button work as intended.
  5. Closing restores focus to the opener, including the opener-removed case where relevant.
  6. Fields, errors, and descriptions are programmatically associated.
  7. Focus remains visible and the dialog remains usable on mobile-sized viewports.
  8. Automated accessibility checks report no serious or critical dialog violations, and a browser test covers open, invalid, valid, and close flows when those states exist.

If a framework or dialog library changes one of these behaviors, document the exception and test the resulting behavior rather than assuming the library is correct.

Workspace
workspace-brfis
Visibility
Public
Created
Last updated
Publish Source
CLI
Badge
workspace-brfis/create-accessible-modals badge