Modal
Keeps a short, reversible task in focus without removing the person from the current context.
Design
Anatomy
Review changes 4
Check the details before applying this update.
The update can be reverted later from the activity log.
6.ds-modal-overlay) — full-screen backdrop that blocks page interaction.2 Container (
.ds-modal) — elevated surface with border-radius and shadow.3 Header (
.ds-modal__header) — contains optional heading, description, and close button.4 Title (
.ds-modal__title) — linked via aria-labelledby.5 Close button (
.ds-modal__close) — dismisses the modal.6 Body (
.ds-modal__body) — scrollable content area.7 Footer (
.ds-modal__footer) — action buttons aligned right.
Interactive example · HTML/CSS/JS
Running with the stable ds-tis/modal JavaScript runtime.
Sizes · HTML/CSS/JS
Three independent dialogs running with the stable HTML/CSS/JavaScript runtime.
Custom body · HTML/CSS/JS
Public Form Field, Input, and Buttons composed inside the Web runtime.
Token mapping
| Property | Token | CSS variable |
|---|---|---|
| overlay background | component.modal.overlay.bg.default | --ds-modal-overlay-bg-default |
| surface | semantic.surface.elevated | --ds-surface-elevated |
| overlay padding | component.modal.overlay.padding.default | --ds-modal-overlay-padding-default |
| overlay z-index | component.modal.overlay.z-index.default | --ds-modal-overlay-z-index-default |
| border-radius | component.modal.radius.default | --ds-modal-radius-default |
| shadow | component.modal.shadow.default | --ds-modal-shadow-default |
| max-width | component.modal.max-width.{sm,md,lg} | --ds-modal-max-width-* |
| header spacing | component.modal.header.{padding-*,gap}.* | --ds-modal-header-*-* |
| body spacing/typography | component.modal.body.{padding-*,font-size,line-height,font-weight}.* | --ds-modal-body-*-* |
| description typography | component.modal.body.{font-size,line-height,font-weight}.* | --ds-modal-body-*-* |
| title typography | component.modal.title.{font-size,line-height,font-weight,letter-spacing}.* | --ds-modal-title-*-* |
| footer spacing | component.modal.footer.{padding-*,gap}.* | --ds-modal-footer-*-* |
| close target | component.modal.close.size.{sm,md,lg} | --ds-modal-close-size-* |
| close icon | component.modal.close.icon-size.{sm,md,lg} | --ds-modal-close-icon-size-* |
| close padding | component.modal.close.padding.{sm,md,lg} | --ds-modal-close-padding-* |
Usage
When to use
- A short edit or review must finish before returning to the previous context.
- The content requires focused attention but does not justify a dedicated page.
When not to use
- The task is long, has multiple steps, or must remain visible alongside the page.
- The confirmation is destructive or critical; that case belongs to the separate Alert Dialog contract.
Implementation
- Status
- Stable
- Distribution
- npm package
Installation
npm install ds-tispnpm add ds-tisyarn add ds-tisbun add ds-tisImports
import 'ds-tis/css'
import { initModals } from 'ds-tis/modal'
initModals()Markup
<button type="button" data-ds-modal-open="review-modal">Revisar alterações</button>
<div class="ds-modal-overlay" id="review-modal" hidden>
<div class="ds-modal ds-modal--md" role="dialog" aria-modal="true" aria-labelledby="review-title">
<div class="ds-modal__header">
<h2 class="ds-modal__title" id="review-title">Revisar alterações</h2>
<button class="ds-modal__close" type="button" aria-label="Fechar modal">…</button>
</div>
<div class="ds-modal__body">Confira os dados antes de continuar.</div>
</div>
</div>Web implementation contract
Required runtime
CSS defines anatomy and visual states, but Modal's accessible contract — focus trap, Escape, backdrop inert, and focus return — depends on the public ds-tis/modal module (initModals / destroyModals). Events: ds-modal-open, ds-modal-close. When Modal is used interactively, initializing the runtime is part of the API; call destroyModals on unmount.
CSS classes
| Class | Description |
|---|---|
ds-modal-overlay | Full-screen backdrop overlay |
ds-modal | Modal dialog container |
ds-modal--sm | Small size (--ds-modal-max-width-sm) |
ds-modal--md | Medium size (--ds-modal-max-width-md) |
ds-modal--lg | Large size (--ds-modal-max-width-lg) |
ds-modal__header | Header area with title and close button |
ds-modal__heading | Optional title and description wrapper |
ds-modal__title | Modal title text |
ds-modal__description | Optional descriptive text in the header |
ds-modal__close | Close button in the header; the element represents the interactive target, not only the icon |
ds-modal__body | Scrollable body content area |
ds-modal__footer | Footer area for action buttons |
Accessibility
Keyboard interaction
| Key | Action |
|---|---|
Tab | Cycles focus inside the modal (focus trap) |
Shift+Tab | Cycles focus backwards inside the modal |
Escape | Closes the modal |
Enter | Activates the focused button |
Focus must be trapped inside the modal while it is open. When the modal closes, focus must return to the element that triggered it.
Accessibility
| WCAG criterion | Requirement | Status |
|---|---|---|
| 2.4.3 Focus Order (A) | Focus moves into the modal on open, is trapped inside, and returns to the trigger on close | ✓ |
| 2.4.11 Focus Appearance (AA) | Focus ring visible on all interactive elements inside the modal | ✓ |
| 4.1.2 Name, Role, Value (A) | role="dialog", aria-modal="true", aria-labelledby pointing to the title | ✓ |
| 1.4.11 Non-text Contrast (AA) | Close button has at least 3:1 contrast ratio against the modal surface | ✓ |
role="dialog" — identifies the modal as a dialog.aria-modal="true" — informs assistive technology that content behind the modal is inert.aria-labelledby — references the modal title element's id.aria-label="Close modal" — required on the close button.
Output responsibility
Initialize the public runtime after render and destroy it when the owning view unmounts.
Validation evidence
Covered by the stable Storybook, public runtime tests when applicable, keyboard scenarios, responsive checks, and Axe.