Skip to the page

MCSS-Lite 0.4.1

GitHub

Modal c-modal

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

  1. Start with <dialog> and add class="c-modal".
  2. Add the required parts: c-modal__container, c-modal__title.
  3. Add the parts you need: c-modal__backdrop, c-modal__header, c-modal__close, c-modal__body, c-modal__footer.
  4. 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>

Elements
ClassOnWhat 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.
States
StateMeaningPair 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
Tokens
TokenValue (light / dark)
--modal-background#ffffff / #14294d
--modal-backdroprgb(15 33 64 / 0.55) / rgb(0 0 0 / 0.72)
--modal-border#1a1d20 / #cfdae5
--modal-radius0.5rem
--modal-shadow0 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-modal1050
--z-index-modal-backdrop1040

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__close needs aria-label="Close" and an SVG icon, not a text glyph.
Keyboard
KeyWhat it does
EscapeCloses the dialog and returns focus to the control that opened it.
TabMoves focus between controls inside the dialog only.

Wording

  1. Title the dialog with the question or task: "Delete this project?"
  2. Button labels repeat the verb: "Delete project" and "Keep project", never "OK" and "Cancel".
  3. Say what happens next in one or two sentences in the body.

All content rules

Fits with

Researched from

Delete project?

Borealis and its 4 pages will be deleted. This can't be undone.