Modal

Focused decisions above the page, escapable by design.

v1.0.0·Updated Jul 8, 2026 · 10:45
Live Demo — Fully Interactive
A focused decision, above the page
Configuration
Size
Tone
Footer
Title
Rename workspace
Visible to your whole team.
Body content sits here — a sentence or two, a form, or a short confirmation.
CancelSave
<Modal size="m" title="Rename workspace" actions={["Cancel", "Save"]} />
Behavior — The Engineering Contract
Props
PropTypeDefaultDescription
openbooleanfalseControlled visibility
onClose(reason) => voidFires on Escape, scrim click, or close button
sizes · m · lmMax-width 340 / 420 / 520px
toneneutral · dangerneutralDanger reddens the title
titlestringLabels the dialog for assistive tech
dismissablebooleantrueWhen false, only an explicit action closes it
Events & keyboard
TriggerResult
EscapeCloses, unless dismissable is false
Scrim clickCloses; clicks inside the card do not
Tab / Shift+TabFocus is trapped within the dialog
On open / closeBody scroll locks; focus moves in, then back to the trigger

A modal interrupts on purpose — reserve it for a single decision that has to happen now. Everything reversible or ambient belongs in a drawer, a popover, or the page itself.

Anatomy
12345
Rename workspace
Visible to your team.
Body content sits here.
CancelSave
1ScrimDims the page and catches the dismiss click. The context is still visible behind it.
2ContainerCentred card, radius 14, one elevation shadow. Width fixed per size.
3HeaderTitle, optional subtitle, and a close button that always offers a way out.
4BodyThe content. Scrolls on its own if it outgrows the viewport.
5FooterRight-aligned actions: ghost escape on the left, filled confirm on the right.
Do's and Don'ts
✓ Do
CancelConfirm

Give every modal an exit: an explicit close, a scrim click, and Escape all lead out.

✕ Don't
Continue

Never trap the user in a dead-end dialog with only one forward path and no way back.

✓ Do
Delete foreverKeep it

Use a modal to confirm the irreversible — the interruption is the point.

✕ Don't
Newsletter · 3s after load

Don't hijack the page for something ambient. A snackbar or inline banner respects the work.

In context: Examples
A · Confirm a decision
Publish v3.2?
This ships to every consumer of the library.
CancelPublish

The classic use: a yes/no that deserves a beat of attention before it happens.

B · A short form
Invite a teammate
name@team.com
CancelSend invite

A few fields at most. Anything longer wants its own page or a drawer.

C · Destructive confirm
Delete project?
42 components will be removed for good.
Keep itDelete

Error red on the primary, a clear escape beside it, and honest copy about what is lost.

Measurements & Tokens
TokenValueRole
--modal-w-s/m/l340 / 420 / 520pxMax-width per size
--modal-bg#101114Card surface
--modal-border#23242ACard hairline
--modal-radius14pxCorner radius
--modal-shadow0 24px 70px rgba(0,0,0,.6)Single elevation shadow
--scrimrgba(4,4,6,.62)Backdrop dim
--modal-title#EB4A4FDanger-tone title
--modal-easecubic-bezier(.22,1,.36,1)Enter transform, 0.24s
Code
<div class="o-modal-scrim is-open">
  <div class="o-modal o-modal--m" role="dialog" aria-modal="true">
    <div class="o-modal__head">
      <div>
        <h2 class="o-modal__title">Rename workspace</h2>
        <p class="o-modal__sub">Visible to your whole team.</p>
      </div>
      <button class="o-modal__close" aria-label="Close">✕</button>
    </div>
    <div class="o-modal__body">Body content.</div>
    <div class="o-modal__divider"></div>
    <div class="o-modal__foot">
      <button class="o-btn o-btn--ghost">Cancel</button>
      <button class="o-btn o-btn--primary">Save</button>
    </div>
  </div>
</div>