Modal
An independent React adapter where Ark UI provides the parts and Zag maintains focus, keyboard behavior, state, and outside-content blocking.
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 · Ark/Zag
Running with the independent Ark UI adapter and Zag behavior.
Sizes · Ark/Zag
Three independent dialogs controlled by Ark UI parts and Zag behavior.
Custom body · Ark/Zag
A real field-and-actions composition inside the Ark/Zag adapter.
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.
When to choose this output
Choose this adapter in projects that adopted Ark UI and Zag as their behavioral architecture. Do not import it into the shadcn/Base UI output.
Implementation
- Status
- Beta
- Distribution
- Source adapter
Adapter source
packages/react/src/ark/modal.jsx
import {
Modal,
ModalBody,
ModalClose,
ModalContent,
ModalDescription,
ModalFooter,
ModalHeader,
ModalHeading,
ModalTitle,
ModalTrigger,
} from '@tis/react/ark/modal'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
Preserve the Ark/Zag semantics and validate Escape, outside interaction, ARIA relationships, and focus return.
Validation evidence
Covered by its independent Storybook output plus browser, responsive, keyboard, Axe, and bundle checks.