Dialog 
<script setup lang="ts">
import {
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogOverlay,
  DialogPortal,
  DialogRoot,
  DialogTitle,
  DialogTrigger,
} from 'radix-vue'
import { Icon } from '@iconify/vue'
</script>
<template>
  <DialogRoot>
    <DialogTrigger
      class="text-grass11 font-semibold shadow-blackA7 hover:bg-mauve3 inline-flex h-[35px] items-center justify-center rounded-[4px] bg-white px-[15px] leading-none shadow-[0_2px_10px] focus:shadow-[0_0_0_2px] focus:shadow-black focus:outline-none"
    >
      Edit profile
    </DialogTrigger>
    <DialogPortal>
      <DialogOverlay class="bg-blackA9 data-[state=open]:animate-overlayShow fixed inset-0 z-30" />
      <DialogContent
        class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white p-[25px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none z-[100]"
      >
        <DialogTitle class="text-mauve12 m-0 text-[17px] font-semibold">
          Edit profile
        </DialogTitle>
        <DialogDescription class="text-mauve11 mt-[10px] mb-5 text-[15px] leading-normal">
          Make changes to your profile here. Click save when you're done.
        </DialogDescription>
        <fieldset class="mb-[15px] flex items-center gap-5">
          <label class="text-grass11 w-[90px] text-right text-[15px]" for="name"> Name </label>
          <input
            id="name"
            class="text-grass11 shadow-green7 focus:shadow-green8 inline-flex h-[35px] w-full flex-1 items-center justify-center rounded-[4px] px-[10px] text-[15px] leading-none shadow-[0_0_0_1px] outline-none focus:shadow-[0_0_0_2px]"
            defaultValue="Pedro Duarte"
          >
        </fieldset>
        <fieldset class="mb-[15px] flex items-center gap-5">
          <label class="text-grass11 w-[90px] text-right text-[15px]" for="username"> Username </label>
          <input
            id="username"
            class="text-grass11 shadow-green7 focus:shadow-green8 inline-flex h-[35px] w-full flex-1 items-center justify-center rounded-[4px] px-[10px] text-[15px] leading-none shadow-[0_0_0_1px] outline-none focus:shadow-[0_0_0_2px]"
            defaultValue="@peduarte"
          >
        </fieldset>
        <div class="mt-[25px] flex justify-end">
          <DialogClose as-child>
            <button
              class="bg-green4 text-green11 hover:bg-green5 focus:shadow-green7 inline-flex h-[35px] items-center justify-center rounded-[4px] px-[15px] font-semibold leading-none focus:shadow-[0_0_0_2px] focus:outline-none"
            >
              Save changes
            </button>
          </DialogClose>
        </div>
        <DialogClose
          class="text-grass11 hover:bg-green4 focus:shadow-green7 absolute top-[10px] right-[10px] inline-flex h-[25px] w-[25px] appearance-none items-center justify-center rounded-full focus:shadow-[0_0_0_2px] focus:outline-none"
          aria-label="Close"
        >
          <Icon icon="lucide:x" />
        </DialogClose>
      </DialogContent>
    </DialogPortal>
  </DialogRoot>
</template>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 
Nested dialog 
You can nest multiple layers of dialogs.
<script setup lang="ts">
import {
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogOverlay,
  DialogPortal,
  DialogRoot,
  DialogTitle,
  DialogTrigger,
} from 'radix-vue'
import { Icon } from '@iconify/vue'
</script>
<template>
  <div>
    <DialogRoot>
      <DialogTrigger
        class="text-grass11 font-semibold shadow-blackA7 hover:bg-mauve3 inline-flex h-[35px] items-center justify-center rounded-[4px] bg-white px-[15px] leading-none shadow-[0_2px_10px] focus:shadow-[0_0_0_2px] focus:shadow-black focus:outline-none"
      >
        Open Dialog
      </DialogTrigger>
      <DialogPortal>
        <DialogOverlay class="bg-blackA9 data-[state=open]:animate-overlayShow fixed inset-0 z-30" />
        <DialogContent
          class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white p-[25px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none z-[100]"
        >
          <DialogTitle class="text-mauve12 m-0 text-[17px] font-semibold">
            First Dialog
          </DialogTitle>
          <DialogDescription class="text-mauve11 mt-[10px] mb-5 text-[15px] leading-normal">
            First dialog.
          </DialogDescription>
          <div class="mt-[25px] flex gap-4 justify-end">
            <DialogClose as-child>
              <button
                class="bg-green4 text-green11 hover:bg-green5 focus:shadow-green7 inline-flex h-[35px] items-center justify-center rounded-[4px] px-[15px] font-semibold leading-none focus:shadow-[0_0_0_2px] focus:outline-none"
              >
                Close
              </button>
            </DialogClose>
            <DialogRoot>
              <DialogTrigger
                class="bg-green9 font-semibold shadow-blackA7 hover:bg-green10  inline-flex h-[35px] items-center justify-center rounded-[4px] text-white px-[15px] leading-none shadow-[0_2px_10px] focus:shadow-[0_0_0_2px] focus:shadow-black focus:outline-none"
              >
                Open second
              </DialogTrigger>
              <DialogPortal>
                <DialogOverlay class="bg-blackA9 data-[state=open]:animate-overlayShow fixed inset-0 z-30" />
                <DialogContent
                  class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white p-[25px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none z-[100]"
                >
                  <DialogTitle class="text-mauve12 m-0 text-[17px] font-semibold">
                    Second Dialog
                  </DialogTitle>
                  <DialogDescription class="text-mauve11 mt-[10px] mb-5 text-[15px] leading-normal">
                    Second dialog.
                  </DialogDescription>
                  <div class="flex justify-end">
                    <DialogClose as-child>
                      <button
                        class="bg-green4 text-green11 hover:bg-green5 focus:shadow-green7 inline-flex h-[35px] items-center justify-center rounded-[4px] px-[15px] font-semibold leading-none focus:shadow-[0_0_0_2px] focus:outline-none"
                      >
                        Close
                      </button>
                    </DialogClose>
                  </div>
                </DialogContent>
              </DialogPortal>
            </DialogRoot>
          </div>
          <DialogClose
            class="text-grass11 hover:bg-green4 focus:shadow-green7 absolute top-[10px] right-[10px] inline-flex h-[25px] w-[25px] appearance-none items-center justify-center rounded-full focus:shadow-[0_0_0_2px] focus:outline-none"
            aria-label="Close"
          >
            <Icon icon="lucide:x" />
          </DialogClose>
        </DialogContent>
      </DialogPortal>
    </DialogRoot>
  </div>
</template>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>