StableComponent · since 0.1.0
A dialog over the page that blocks interaction until dismissed. Works on <dialog> (preferred, opened with showModal()) or on a <div role="dialog">.
Pick this part
Pick c-modal for
- Confirming a destructive or irreversible action, such as deleting a project.
- A short task that must not lose the page behind it, such as renaming an item.
- Warning that unsaved work will be lost.
Pick another part for
- Long or scrolling content, or anything with its own navigation: make it a page.
- Success messages and tips: show them inline on the page.
- Opening a dialog from a dialog: keep it to one level where you can, two at most.
Build it
- Start with
<dialog>and addclass="c-modal". - Add the required parts:
c-modal__container,c-modal__title. - Add the parts you need:
c-modal__backdrop,c-modal__header,c-modal__close,c-modal__body,c-modal__footer. - Add
data-state="closed"when hidden (div form). When closed, return focus to the control that opened the modal.
Show markup 16 lines
<div class="c-modal" role="dialog" aria-modal="true" aria-labelledby="delete-title">
<div class="c-modal__backdrop"></div>
<div class="c-modal__container">
<header class="c-modal__header">
<h2 class="c-modal__title" id="delete-title">Delete project?</h2>
<button type="button" class="c-modal__close" aria-label="Close"><svg viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 5l10 10M15 5L5 15"/></svg></button>
</header>
<div class="c-modal__body">
<p class="u-margin-0">Atlas and its 12 pages will be deleted. This can't be undone.</p>
</div>
<footer class="c-modal__footer">
<button type="button" class="c-button c-button--ghost">Keep project</button>
<button type="button" class="c-button c-button--danger">Delete project</button>
</footer>
</div>
</div>
Check your build
Each piece that doesn't fit was run through mcss-lite validate when this page was built. The output below is real.
Fits
Use <dialog> opened with showModal(), named by its title, with verb buttons.
Show markup 13 lines
<dialog class="c-modal" aria-labelledby="leave-title">
<div class="c-modal__container">
<div class="c-modal__header">
<h2 class="c-modal__title" id="leave-title">Discard your changes?</h2>
</div>
<div class="c-modal__footer">
<div class="l-cluster">
<button type="button" class="c-button c-button--ghost">Keep editing</button>
<button type="button" class="c-button c-button--danger">Discard changes</button>
</div>
</div>
</div>
</dialog>
validate: 0 issues
Doesn't fit
Don't build a dialog from a bare <div>: no focus handling, no Escape, nothing announced.
<div class="c-modal">
<div class="c-modal__container">Discard your changes?</div>
</div>
validate
- warning c-modal needs role="dialog" (or use <dialog>), aria-modal="true" and aria-labelledby.
[a11y]
Fits
Label the close button.
Show markup 8 lines
<dialog class="c-modal" aria-labelledby="share-title">
<div class="c-modal__container">
<div class="c-modal__header">
<h2 class="c-modal__title" id="share-title">Share project</h2>
<button type="button" class="c-modal__close" aria-label="Close"><svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8" fill="none" stroke="currentColor" stroke-width="2"/></svg></button>
</div>
</div>
</dialog>
validate: 0 issues
Doesn't fit
Don't leave an icon-only close button without a name.
<dialog class="c-modal" aria-labelledby="share-title2">
<div class="c-modal__container">
<h2 class="c-modal__title" id="share-title2">Share project</h2>
<button type="button" class="c-modal__close">×</button>
</div>
</dialog>
validate
- warning c-modal__close needs an accessible name, e.g. aria-label="Close" or "Dismiss".
[a11y]
Spec sheet
Apply to <dialog> <div>
| Class | On | What it does |
|---|---|---|
c-modal__backdrop | <div> | Dimmed layer behind the dialog; clicking it may close the modal. |
c-modal__container required | <div> <form> | The dialog panel. |
c-modal__header | <header> <div> | Holds the title and close button. |
c-modal__title required | <h2> <h3> | Dialog heading, referenced by aria-labelledby. |
c-modal__close | <button> | Icon button that closes the dialog. |
c-modal__body | <div> | Dialog content. |
c-modal__footer | <footer> <div> | Actions, right-aligned. Put the primary action last. |
| State | Meaning | Pair it with |
|---|---|---|
data-state="closed" | Hidden (div form). With <dialog>, use the open attribute / showModal() instead. | When closed, return focus to the control that opened the modal. |
Show tokens 7
| Token | Value (light / dark) |
|---|---|
--modal-background | #ffffff / #14294d |
--modal-backdrop | rgb(15 33 64 / 0.55) / rgb(0 0 0 / 0.72) |
--modal-border | #1a1d20 / #cfdae5 |
--modal-radius | 0.5rem |
--modal-shadow | 0 2px 0 #1a1d20, 0 10px 24px -4px rgb(26 29 32 / 0.22) / 0 2px 0 rgb(207 218 229 / 0.5), 0 12px 28px -4px rgb(0 0 0 / 0.6) |
--z-index-modal | 1050 |
--z-index-modal-backdrop | 1040 |
Accessibility
- Prefer <dialog class="c-modal"> opened with showModal(): the browser then handles focus, Escape and inertness. With a
<div>, set role="dialog", aria-modal="true" and manage focus yourself. - Reference the title with aria-labelledby.
- With the
<div>form, make the page behind inert and lock scrolling while open, trap focus, close on Escape, and return focus to the opener. c-modal__closeneeds aria-label="Close" and an SVG icon, not a text glyph.
| Key | What it does |
|---|---|
| Escape | Closes the dialog and returns focus to the control that opened it. |
| Tab | Moves focus between controls inside the dialog only. |
Wording
- Title the dialog with the question or task: "Delete this project?"
- Button labels repeat the verb: "Delete project" and "Keep project", never "OK" and "Cancel".
- Say what happens next in one or two sentences in the body.