A centered dialog over a scrim for a short interruption: an icon and title, a body, and a footer of actions.
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
sizesmbk-modal--size-<value> | smmdlg | sm | The greatest width the panel takes. |
inlinesmbk-modal--inline | On or off | Off | Shown in place with no scrim, for previews and documentation. |
Examples
Usage
Icon and title, a body, a rule and the actions. Shown in place here.
Sizes
Three greatest widths.
Without icon
The icon is optional.
Guidelines
Purpose
Stops the reader for one short thing: a confirmation, a small form, a choice that must be made before going on. The page behind is dimmed and cannot be used until the modal closes. A drawer is for longer tasks beside the page; a modal is for a moment in the middle of it.
When to use
- Confirming something that cannot be undone.
- A form of two or three fields that belongs to the page behind.
- A choice the reader must make before the page can continue.
When not to use
Usage rules
Do
- Name the primary action after what it does: "Delete", not "OK".
- Put the primary action last and give it the brand tone, or the danger tone for a destructive one.
Don't
- Stack modals.
- Use a modal to show a picture. Use the page.
Name the main button after what it does, so the reader knows the result before they press it.
An "OK" button makes the reader reread the question to learn what pressing it will do.
Anatomy
- The block, a native dialog element centered over a scrim.
- The header part: an optional icon part and the title part on one line, and the close part in the modal's top corner, the large inset from its top and end edges, overlapping the end of that line; the title keeps clear of it.
- The body part, which scrolls when tall.
- A divider and the footer part, optional, holding the actions aligned to the end.
Behavior
Opens modally. Escape, the close button and a click on the scrim all ask to close: a modal that holds its own open state closes itself, and one the page holds waits for the page to close it. A click anywhere on the panel, its padding and edge included, never asks, nor does a press that starts on the panel and ends on the scrim, as when selecting words. With a popover, a dropdown or a list open inside the modal, Escape and a click on the scrim close that first, and the modal stays. The size axis sets the greatest width; the panel shrinks with the window. It fades in and rises a spacing step into place over a scrim that fades in, and fades out a little faster as it closes; under reduced motion it still fades in and out, in place, without rising or dropping. Inline modals do not move.
The inline flag renders the panel in place with no scrim, so it can be seen in the Showroom and in documentation.
The React wrapper opens and closes it with the dialog's own modal open method, as other layers open: given open, the page holds the open state; without it, the modal holds its own, closed unless given defaultOpen. Asking to close calls onOpenChange(false). Do not also set the dialog's open attribute.
Accessibility
What it does for you
- It is a native dialog opened modally: focus moves inside, is held there, and returns to the opener on close.
- It is labeled by its title, a second-level heading unless
headingLevelgives another, for a modal opened from deeper in the page's outline. - The close button is icon-only and named "Close". Every word it says by itself, such as the close button's name, keeps its English unless the page gives its own, so a page in another language can change it.
- Escape, the close button and a click on the scrim ask to close; the page decides by handling the request.
What you must do
- Give the modal a title, and set
headingLevelwhere the second level does not fit the page's outline. - If the close button is hidden, put an action in the footer that closes the modal.
- Do not open a modal without the reader having done something.
- Give the close button's name in the page's own language when the page is not in English.
How to test
- Press the control that opens it: focus moves into the modal. Tab and Shift + Tab stay inside it, and Escape asks to close it. After it closes, focus is back on the control that opened it.
- With a screen reader, it is announced as a dialog by its title, and the close button is read as "Close".
- With text at 200% and the page 320 pixels wide, the panel shrinks with the window and its content stays reachable.
- In a forced-color mode, the focus ring on its buttons and the panel's edge stay visible.
Keyboard
| Key | What it does |
|---|---|
| TaborShift+Tab | Moves focus between the controls inside the modal and holds it there. |
| Escape | Asks to close the modal. Inside it, an open popover, dropdown or list closes first. |
| EnterorSpace | On the close button or a footer button, presses it. |
Source
The files the modal is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-modal — Modal. Figma "Modal" 4212:26125 (Size: sm, md, lg).
* A centered dialog over a scrim, built on the native dialog element. The three
* widths are the modal size tokens, carried from Figma's frames, used as
* maximums so the panel shrinks with the window. The sections are spaced with
* the extra-large stack step, as Figma binds them.
* The inline flag shows the panel in place, for previews and documentation.
* The panel fades and rises into place over a fading scrim (motion rules at the end). */
.smbk-modal {
box-sizing: border-box;
position: fixed;
inset: 0;
z-index: var(--smbk-layer-modal);
display: none;
flex-direction: column;
gap: var(--smbk-space-stack-xl);
width: 100%;
max-width: var(--smbk-size-modal-sm);
max-height: 100%;
margin: auto;
padding: var(--smbk-space-inset-2xl);
border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-rim);
border-radius: var(--smbk-radius-container-lg);
background: var(--smbk-color-surface-default);
color: var(--smbk-color-text-default);
box-shadow: var(--smbk-elevation-overlay);
}
.smbk-modal[open] {
display: flex;
}
/* The icon and the title on one line, as Figma draws them. The title keeps
clear of the close button, which overlaps the row's end (see below). */
.smbk-modal__header {
position: relative;
display: flex;
align-items: center;
gap: var(--smbk-space-inline-lg);
padding-inline-end: calc(var(--smbk-size-control-md) - var(--smbk-space-inset-2xl) + var(--smbk-space-inset-lg));
}
.smbk-modal__icon {
flex-shrink: 0;
font-size: var(--smbk-size-icon-lg);
}
/* The alert dialog (alert-dialog.css, drawn by this stylesheet) warns in the danger color while its confirm action is
the danger one, as its Figma drawing has it; with a brand confirm action its icon keeps the modal's color. */
.smbk-alert-dialog:has(.smbk-btn--tone-danger) .smbk-modal__icon {
color: var(--smbk-color-feedback-danger-icon);
}
.smbk-modal__title {
margin: 0;
font-family: var(--smbk-typography-heading-2xs-bold-font-family);
font-size: var(--smbk-typography-heading-2xs-bold-font-size);
font-weight: var(--smbk-typography-heading-2xs-bold-font-weight);
line-height: var(--smbk-typography-heading-2xs-bold-line-height);
letter-spacing: var(--smbk-typography-heading-2xs-bold-letter-spacing);
}
/* The close button sits in the modal's corner, the large inset from its top and end edges, over the
padding, as Figma places it: the header sits the modal's own padding (and its hairline
edge) in from those edges, so the button is moved back out by the difference. */
.smbk-modal__close {
position: absolute;
top: calc(var(--smbk-space-inset-lg) - var(--smbk-space-inset-2xl) - var(--smbk-border-width-hairline));
inset-inline-end: calc(var(--smbk-space-inset-lg) - var(--smbk-space-inset-2xl) - var(--smbk-border-width-hairline));
}
.smbk-modal__body {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
/* The system's scrollbar: thin, in the subtle border color, on a clear track. */
scrollbar-width: thin;
scrollbar-color: var(--smbk-color-border-subtle) transparent;
color: var(--smbk-color-text-subtle);
font-family: var(--smbk-typography-body-sm-font-family);
font-size: var(--smbk-typography-body-sm-font-size);
font-weight: var(--smbk-typography-body-sm-font-weight);
line-height: var(--smbk-typography-body-sm-line-height);
letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}
/* The first thing in it, after the scrollbar the script adds (scrollbar.ts). */
.smbk-modal__body > :first-child,
.smbk-modal__body > .smbk-scrollbar:first-child + * {
margin-top: 0;
}
.smbk-modal__body > :last-child {
margin-bottom: 0;
}
.smbk-modal__footer {
display: flex;
justify-content: flex-end;
gap: var(--smbk-space-inline-lg);
}
/* ---- sizes ---- */
.smbk-modal--size-md {
max-width: var(--smbk-size-modal-md);
}
.smbk-modal--size-lg {
max-width: var(--smbk-size-modal-lg);
}
/* ---- inline: shown in place, no scrim ---- */
.smbk-modal--inline {
position: relative;
inset: auto;
z-index: auto;
margin: 0;
}
/* ---- over the page: the scrim, the motion (the panel fades in and rises a spacing step into place over a scrim
that fades in, and fades out dropping a smaller step; under reduced motion it fades in place) and the page held
still behind it are the dialogs' own, shared with the drawer and the command palette: dialog/dialog.css. Inline
modals do not move. ---- */Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| color.overlay.scrim--smbk-color-overlay-scrim | light: #00000080dark: #00000099changes with theme | Color | |
| motion.exit.duration--smbk-motion-exit-duration | 200ms | Motion | |
| motion.exit.easing--smbk-motion-exit-easing | cubic-bezier(0.7, 0, 0.84, 0) | Motion | |
| motion.enter.duration--smbk-motion-enter-duration | 320ms | Motion | |
| motion.enter.easing--smbk-motion-enter-easing | cubic-bezier(0.16, 1, 0.3, 1) | Motion | |
| space.8--smbk-space-8 | 8px | Spacing | |
| space.16--smbk-space-16 | 16px | Spacing | |
| layer.modal--smbk-layer-modal | 1300 | Stacking order | |
| space.stack.xl--smbk-space-stack-xl | comfortable: 24pxcompact: 16pxchanges with density | Spacing | |
| size.modal.sm--smbk-size-modal-sm | 400px | Size | |
| space.inset.2xl--smbk-space-inset-2xl | comfortable: 24pxcompact: 16pxchanges with density | Spacing | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.rim--smbk-color-border-rim | light: #ffffffe6dark: #ffffff0dchanges with theme | Color | |
| radius.container.lg--smbk-radius-container-lg | 16px | Radius | |
| color.surface.default--smbk-color-surface-default | light: #f3f5f8dark: #181a1cchanges with theme | Color | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| elevation.overlay--smbk-elevation-overlay | light: 0px 12px 16px 2px #373d431a, 0px 2px 8px 0px #373d4326dark: 0px 12px 16px 2px #00000026, 0px 2px 8px 0px #00000033 | Elevation | |
| space.inline.lg--smbk-space-inline-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| size.control.md--smbk-size-control-md | comfortable: 40pxcompact: 32pxchanges with density | Size | |
| space.inset.lg--smbk-space-inset-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| size.icon.lg--smbk-size-icon-lg | 24px | Size | |
| color.feedback.danger.icon--smbk-color-feedback-danger-icon | light: #cc0d0ddark: #fe7d6dchanges with theme | Color | |
| typography.heading-2xs-bold--smbk-typography-heading-2xs-bold-font-family--smbk-typography-heading-2xs-bold-font-size--smbk-typography-heading-2xs-bold-font-weight--smbk-typography-heading-2xs-bold-line-height--smbk-typography-heading-2xs-bold-letter-spacing--smbk-typography-heading-2xs-bold-text-transform | fontFamily: Funnel Display, Funnel Display Fallback, system-ui, sans-seriffontSize: min: 18px · max: 24pxfontWeight: 700lineHeight: 1.2letterSpacing: 0px | The quick brown fox | Typography |
| color.border.subtle--smbk-color-border-subtle | light: #c4cdd8dark: #2d3237changes with theme | Color | |
| color.text.subtle--smbk-color-text-subtle | light: #4e555edark: #9ba5b1changes with theme | Color | |
| typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| size.modal.md--smbk-size-modal-md | 560px | Size | |
| size.modal.lg--smbk-size-modal-lg | 800px | Size |
Manifest
{
"name": "modal",
"block": "smbk-modal",
"summary": "A centered dialog over a scrim for a short interruption: an icon and title, a body, and a footer of actions.",
"adapt": "css",
"axes": {
"size": {
"values": [
"sm",
"md",
"lg"
],
"default": "sm",
"description": "The greatest width the panel takes."
}
},
"flags": {
"inline": {
"description": "Shown in place with no scrim, for previews and documentation."
}
},
"parts": [
"header",
"icon",
"title",
"close",
"body",
"footer"
],
"figmaNodeId": "4212:26125"
}