Dialog
Features
- Supports modal and non-modal modes.
- Focus is automatically trapped when modal.
- Can be controlled or uncontrolled.
- Manages screen reader announcements with
TitleandDescriptioncomponents. - Esc closes the component automatically.
Installation
Install the component from your command line.
npm install radix-vueAnatomy
Import all parts and piece them together.
<script setup>
import {
DialogClose,
DialogContent,
DialogDescription,
DialogOverlay,
DialogPortal,
DialogRoot,
DialogTitle,
DialogTrigger,
} from 'radix-vue'
</script>
<template>
<DialogRoot>
<DialogTrigger />
<DialogPortal>
<DialogOverlay />
<DialogContent>
<DialogTitle />
<DialogDescription />
<DialogClose />
</DialogContent>
</DialogPortal>
</DialogRoot>
</template>API Reference
Root
Contains all the parts of a dialog
| Prop | Type | Default |
|---|---|---|
defaultOpen | boolean | |
open | boolean | |
modal | boolean | true |
| Emit | Type |
|---|---|
@update:open | (open: boolean) => void |
Trigger
The button that opens the dialog
| Prop | Type | Default |
|---|---|---|
as | string | Component | button |
asChild | boolean | false |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
Portal
When used, portals your overlay and content parts into the body.
| Prop | Type | Default |
|---|---|---|
to | string | HTMLElement | body |
Overlay
A layer that covers the inert portion of the view when the dialog is open.
| Prop | Type | Default |
|---|---|---|
as | string | Component | div |
asChild | boolean | false |
forceMount | boolean |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
Content
Contains content to be rendered in the open dialog
| Prop | Type | Default |
|---|---|---|
as | string | Component | div |
asChild | boolean | false |
forceMount | boolean |
| Emit | Type |
|---|---|
@openAutoFocus | (event: Event) => void |
@closeAutoFocus | (event: Event) => void |
@escapeKeyDown | (event: KeyboardEvent) => void |
@pointerDownOutside | function |
@interactOutside | function |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
Close
The button that closes the dialog
| Prop | Type | Default |
|---|---|---|
as | string | Component | button |
asChild | boolean | false |
Title
An accessible title to be announced when the dialog is opened.
If you want to hide the title, wrap it inside our Visually Hidden utility like this <VisuallyHidden asChild>.
| Prop | Type | Default |
|---|---|---|
as | string | Component | h2 |
asChild | boolean | false |
Description
An optional accessible description to be announced when the dialog is opened.
If you want to hide the description, wrap it inside our Visually Hidden utility like this <VisuallyHidden asChild>. If you want to remove the description entirely, remove this part and pass aria-describedby="undefined} to DialogContent.
| Prop | Type | Default |
|---|---|---|
as | string | Component | p |
asChild | boolean | false |
Examples
Close after asynchronous form submission
Use the controlled props to programmatically close the Dialog after an async operation has completed.
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'radix-vue'
const wait = () => new Promise(resolve => setTimeout(resolve, 1000))
const open = ref(false)
</script>
<template>
<DialogRoot v-model:open="open">
<DialogTrigger>Open</DialogTrigger>
<DialogPortal>
<DialogOverlay />
<DialogContent>
<form
@submit.prevent="
(event) => {
wait().then(() => (open = false));
}
"
>
<!-- some inputs -->
<button type="submit">
Submit
</button>
</form>
</DialogContent>
</DialogPortal>
</DialogRoot>
</template>Scrollable overlay
Move the content inside the overlay to render a dialog with overflow.
// index.vue
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'radix-vue'
import './styles.css'
</script>
<template>
<DialogRoot>
<DialogTrigger />
<DialogPortal>
<DialogOverlay class="DialogOverlay">
<DialogContent class="DialogContent">
...
</DialogContent>
</DialogOverlay>
</DialogPortal>
</DialogRoot>
</template>/* styles.css */
.DialogOverlay {
background: rgba(0 0 0 / 0.5);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: grid;
place-items: center;
overflow-y: auto;
}
.DialogContent {
min-width: 300px;
background: white;
padding: 30px;
border-radius: 4px;
}Custom portal container
Customise the element that your dialog portals into.
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'radix-vue'
const container = ref(null)
</script>
<template>
<div>
<DialogRoot>
<DialogTrigger />
<DialogPortal to="container">
<DialogOverlay />
<DialogContent>...</DialogContent>
</DialogPortal>
</DialogRoot>
<div ref="container" />
</div>
</template>Accessibility
Adheres to the Dialog WAI-ARIA design pattern.
Keyboard Interactions
| Key | Description |
|---|---|
Space | Opens/closes the dialog |
Enter | Opens/closes the dialog |
Tab | Moves focus to the next focusable element. |
Shift + Tab | Moves focus to the previous focusable element. |
Esc | Closes the dialog and moves focus to DialogTrigger. |
Custom APIs
Create your own API by abstracting the primitive parts into your own component.
Abstract the overlay and the close button
This example abstracts the DialogOverlay and DialogClose parts.
Usage
<script setup>
import { Dialog, DialogContent, DialogTrigger } from './your-dialog'
</script>
<template>
<Dialog>
<DialogTrigger>Dialog trigger</DialogTrigger>
<DialogContent>Dialog Content</DialogContent>
</Dialog>
</template>Implementation
// your-dialog.ts
export { default as DialogContent } from 'DialogContent.vue'
export { DialogRoot as Dialog, DialogTrigger } from 'radix-vue'<!-- DialogContent.vue -->
<script setup lang="ts">
import { DialogClose, DialogContent, type DialogContentEmits, type DialogContentProps, DialogOverlay, DialogPortal, useEmitAsProps, } from 'radix-vue'
import { Cross2Icon } from '@radix-icons/vue'
const props = defineProps<DialogContentProps>()
const emits = defineEmits<DialogContentEmits>()
const emitsAsProps = useEmitAsProps(emits)
</script>
<template>
<DialogPortal>
<DialogOverlay />
<DialogContent v-bind="{ ...props, ...emitsAsProps }">
<slot />
<DialogClose>
<Cross2Icon />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>
</DialogPortal>
</template>