Focused decisions above the page, escapable by design.
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | false | Controlled visibility |
| onClose | (reason) => void | — | Fires on Escape, scrim click, or close button |
| size | s · m · l | m | Max-width 340 / 420 / 520px |
| tone | neutral · danger | neutral | Danger reddens the title |
| title | string | — | Labels the dialog for assistive tech |
| dismissable | boolean | true | When false, only an explicit action closes it |
| Trigger | Result |
|---|---|
| Escape | Closes, unless dismissable is false |
| Scrim click | Closes; clicks inside the card do not |
| Tab / Shift+Tab | Focus is trapped within the dialog |
| On open / close | Body 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.
Give every modal an exit: an explicit close, a scrim click, and Escape all lead out.
Never trap the user in a dead-end dialog with only one forward path and no way back.
Use a modal to confirm the irreversible — the interruption is the point.
Don't hijack the page for something ambient. A snackbar or inline banner respects the work.
The classic use: a yes/no that deserves a beat of attention before it happens.
A few fields at most. Anything longer wants its own page or a drawer.
Error red on the primary, a clear escape beside it, and honest copy about what is lost.
| Token | Value | Role |
|---|---|---|
| --modal-w-s/m/l | 340 / 420 / 520px | Max-width per size |
| --modal-bg | #101114 | Card surface |
| --modal-border | #23242A | Card hairline |
| --modal-radius | 14px | Corner radius |
| --modal-shadow | 0 24px 70px rgba(0,0,0,.6) | Single elevation shadow |
| --scrim | rgba(4,4,6,.62) | Backdrop dim |
| --modal-title | #EB4A4F | Danger-tone title |
| --modal-ease | cubic-bezier(.22,1,.36,1) | Enter transform, 0.24s |
<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>