Skip to content
The Membrane Palette
Feedbackstable

Dialog

A modal built on native <dialog> — focus trapping, Escape and the backdrop come from the browser; header, body and footer slots.

Examples

Confirmation dialog

Publish this workshop?

It becomes visible to all 3,214 educators in the community, and they'll get an email invitation.

HTML for “Confirmation dialog”
<button class="mp-button" type="button" data-variant="secondary" commandfor="dialog-publish" command="show-modal">Publish workshop…</button>

<dialog class="mp-dialog" id="dialog-publish" data-size="sm" aria-labelledby="dialog-publish-title" closedby="any">
  <div class="mp-dialog__header">
    <h2 class="mp-dialog__title" id="dialog-publish-title">Publish this workshop?</h2>
    <button class="mp-button" type="button" data-variant="ghost" data-size="sm" data-icon-only aria-label="Close" commandfor="dialog-publish" command="close">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>
    </button>
  </div>
  <div class="mp-dialog__body">
    <p>It becomes visible to all 3,214 educators in the community, and they'll get an email invitation.</p>
  </div>
  <div class="mp-dialog__footer">
    <button class="mp-button" type="button" data-variant="ghost" commandfor="dialog-publish" command="close">Not yet</button>
    <button class="mp-button" type="button" commandfor="dialog-publish" command="close">Publish</button>
  </div>
</dialog>

Large reading dialog

AI Literacy I — syllabus

Week 1 — Start from the classroom

Bring one real problem. We map it before we touch a tool.

Week 2 — How models actually work

Tokens, context, and why a confident answer can still be wrong.

HTML for “Large reading dialog”
<button class="mp-button" type="button" data-variant="ghost" commandfor="dialog-syllabus" command="show-modal">Read the syllabus</button>

<dialog class="mp-dialog" id="dialog-syllabus" data-size="lg" aria-labelledby="dialog-syllabus-title" closedby="any">
  <div class="mp-dialog__header">
    <h2 class="mp-dialog__title" id="dialog-syllabus-title">AI Literacy I — syllabus</h2>
    <button class="mp-button" type="button" data-variant="ghost" data-size="sm" data-icon-only aria-label="Close" commandfor="dialog-syllabus" command="close">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>
    </button>
  </div>
  <div class="mp-dialog__body mp-prose">
    <h3>Week 1 — Start from the classroom</h3>
    <p>Bring one real problem. We map it before we touch a tool.</p>
    <h3>Week 2 — How models actually work</h3>
    <p>Tokens, context, and why a confident answer can still be wrong.</p>
  </div>
  <div class="mp-dialog__footer">
    <button class="mp-button" type="button" commandfor="dialog-syllabus" command="close">Done</button>
  </div>
</dialog>

Guidance

The native <dialog> element opened with showModal() traps focus, makes the page behind it inert, closes on Escape and restores focus on close. The component adds the elevated surface (--color-bg-elevated, which lightens in dark mode), the blurred scrim, and an entrance transition.

Opening and closing

  • Invoker attributes (no script): <button commandfor="my-dialog" command="show-modal"> and command="close" inside. components/membrane.js polyfills these where the browser lacks them.
  • Script: document.getElementById('my-dialog').showModal().
  • Form: a <form method="dialog"> inside closes it on submit.
  • closedby="any" also closes on a backdrop click (polyfilled by membrane.js).

Accessibility

  • Always open with showModal() (or command="show-modal"), never by toggling the open attribute — only the modal path traps focus and makes the page inert.
  • Label it with aria-labelledby pointing at the title.
  • Put focus somewhere sensible: by default the first focusable element; add autofocus to the least destructive button for confirmations.
  • The page behind an open modal stops scrolling.

Manual screen-reader and usability checks have not been recorded for this inventory. See the accessibility evidence for automated coverage, remaining checks and the result-recording protocol.

API

Dialog classes
ClassKind
.mp-dialogroot
.mp-dialog__headerpart of .mp-dialog
.mp-dialog__titlepart of .mp-dialog
.mp-dialog__bodypart of .mp-dialog
.mp-dialog__footerpart of .mp-dialog
Dialog attributes
AttributeValues
data-sizesm · lg

States styled: :modal [open]. Set the native attribute or ARIA state; there are no state classes.

Dialog component tokens
TokenDefaultLight · dark
--mp-dialog-width35rem—
--mp-dialog-bgvar(--color-bg-elevated)#FAF6EE · #27272D
--mp-dialog-fgvar(--color-text-primary)#2A2622 · #F0EBE1
--mp-dialog-bodyvar(--color-text-secondary)#5C564E · #A39D93

Design tokens consumed: --blur-backdrop --border-default --color-bg-elevated --color-overlay-bg --color-text-primary --color-text-secondary --duration-fast --duration-normal --elevation-modal --font-sans --font-serif --leading-body --leading-subheading --radius-xl --space-2 --space-3 --space-4 --space-6 --text-body --text-h3 --tracking-subheading --transition-enter --transition-exit --weight-semibold

Verified contrast

The build measures each pair for the defaults and for every tone, variant and state that reassigns a token, over page and card surfaces. The ratios below are the worst case in each mode; a pair below its minimum stops the build.

Dialog contrast pairs
UsePairLightDarkMinimum
dialog title--mp-dialog-fg on --mp-dialog-bg13.9312.507.0
dialog body--mp-dialog-body on --mp-dialog-bg6.735.524.5

Source