Modal
Modals are overlay dialogs that appear on top of the main content to display important information, collect user input, or confirm actions. They temporarily block interaction with the underlying page until dismissed.
The React Modal wraps Radix UI’s Dialog primitive. It accepts a trigger prop (ReactNode), a slot prop for body content, and buttonGroup for action buttons. Controlled state is managed via isOpen and onOpenChange.
The Vue Modal uses Teleport for portal rendering and named slots (#trigger, default slot for body content). Action buttons are configured via buttonGroup. Controlled state is managed via isOpen prop and open-change event.
Loading Code Block...Full example
Section titled “Full example”A default modal with header, description, scrollable body content, and action buttons.
Loading Code Block...The modal supports two types:
| Type | Description |
|---|---|
default | Flexible container with custom content slot, header, description, and action buttons |
alert | Centered confirmation/notification with state icon, message, and stretch-layout buttons |
Loading Code Block...| Size | Width |
|---|---|
sm | 520px |
md | 605px |
lg | 720px |
Use the dropdown in the interactive demo below to toggle between sm, md, and lg — the code snippet updates to match.
Loading Code Block...Alert states
Section titled “Alert states”Alert modals support three visual states, each with a corresponding coloured icon:
| State | Icon | Colour |
|---|---|---|
success | Check mark | Green (theme-icon-accent-primary) |
warning | Exclamation mark | Yellow (theme-icon-warning) |
destructive | Close / X | Red (theme-icon-danger) |
When state is destructive and type is alert, the primary button automatically receives destructive: true styling.
Loading Code Block...Progress tracking
Section titled “Progress tracking”Default modals support a built-in progress bar at the top, useful for multi-step wizards and onboarding flows. The bar is rendered when showProgress is true and is sized from currentStep / totalSteps (or from progressValue when provided directly).
Loading Code Block...Static backdrop
Section titled “Static backdrop”When staticBackdrop is true, clicking outside the modal does not close it. Users must interact with a button or the close icon to dismiss.
Loading Code Block...Vue events
Section titled “Vue events”| Props | Default | Type | Description |
|---|---|---|---|
| open-change | - | (value: boolean) => void | Emitted when the modal's open state changes. Required for controlled mode with isOpen. |
| modal-open | - | () => void | Emitted when the modal opens. |
| modal-close | - | () => void | Emitted when the modal closes. |
Vue slots
Section titled “Vue slots”| Props | Default | Type | Description |
|---|---|---|---|
| #trigger | - | slot | Named slot for the element that opens the modal when clicked. |
| default | - | slot | Default slot for body content (equivalent to React's slot prop). |
Accessibility
Section titled “Accessibility”- The dialog container exposes
role="dialog"andaria-modal="true"in both frameworks (React via Radix’sDialog.Content; Vue via the native<dialog>element) - React (Radix) traps focus inside the dialog — Tab cycles through interactive elements inside the modal and never escapes to the page underneath. Vue currently focuses the first focusable on open and restores focus to the previously active element on close, but does not intercept Tab — keyboard users can tab past the last focusable inside the modal into the page below
- Pressing
Escapecloses the modal in both frameworks (unlessstaticBackdropis true) - Vue wires
aria-labelledby/aria-describedbyto the renderedheaderText/subtextIDs when those props are present - The close button has
aria-label="Close modal"for screen readers - Body scroll is locked when the modal is open (
overflow: hiddenin Vue; equivalent body scroll lock applied by Radix in React)