Skip to content

Dialog ​

A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.

Features ​

  • Supports modal and non-modal modes.
  • Focus is automatically trapped when modal.
  • Can be controlled or uncontrolled.
  • Manages screen reader announcements with Title andDescription components.
  • Esc closes the component automatically.

Installation ​

Install the component from your command line.

bash
npm install radix-vue

Anatomy ​

Import all parts and piece them together.

vue
<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

PropTypeDefault
defaultOpen
boolean
open
boolean
modal
boolean
true
EmitType
@update:open
(open: boolean) => void

Trigger ​

The button that opens the dialog

PropTypeDefault
asChild
boolean
false
Data AttributeValue
[data-state]"open" | "closed"

Portal ​

When used, portals your overlay and content parts into the body.

PropTypeDefault
to
string | HTMLElement
body

Overlay ​

A layer that covers the inert portion of the view when the dialog is open.

PropTypeDefault
asChild
boolean
false
Data AttributeValue
[data-state]"open" | "closed"

Content ​

Contains content to be rendered in the open dialog

PropTypeDefault
asChild
boolean
false
EmitType
@openAutoFocus
(event: Event) => void
@closeAutoFocus
(event: Event) => void
@escapeKeyDown
(event: KeyboardEvent) => void
Data AttributeValue
[data-state]"open" | "closed"

Close ​

The button that closes the dialog

PropTypeDefault
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>.

PropTypeDefault
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.

PropTypeDefault
asChild
boolean
false

Examples ​

Close after asynchronous form submission ​

Use the controlled props to programmatically close the Dialog after an async operation has completed.

vue
<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.

vue
// 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>
css
/* 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.

vue
<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 ​

KeyDescription
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 ​

vue
<script setup>
import { Dialog, DialogContent, DialogTrigger } from './your-dialog'
</script>

<template>
  <Dialog>
    <DialogTrigger>Dialog trigger</DialogTrigger>
    <DialogContent>Dialog Content</DialogContent>
  </Dialog>
</template>

Implementation ​

ts
// your-dialog.ts
export { default as DialogContent } from 'DialogContent.vue'
export { DialogRoot as Dialog, DialogTrigger } from 'radix-vue'
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>