Modal

Dialog overlay for focused user interactions and important content

Usage

import { Modal } from '@hareui/vue'
<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template>  <Modal>    <Button variant="secondary">      Open Modal    </Button>    <template #icon>      <Icon class="text-foreground" icon="gravity-ui:rocket" />    </template>    <template #title>      Welcome to HareUI    </template>    <template #body>      <p>        A beautiful, fast, and modern Vue UI library for building accessible and        customizable web applications with ease.      </p>    </template>    <template #footer="{ close }">      <Button class="w-full" @click="close">        Continue      </Button>    </template>  </Modal></template>

Anatomy

A single Modal component owns the whole structure — the first child is the trigger (as-child, merged directly onto its own root element), everything else is supplied through named slots.

<Modal>
  <Button>Open Modal</Button>
  <template #icon><!-- optional icon --></template>
  <template #title>Heading</template>
  <template #description><p>Optional supporting text</p></template>
  <template #body>Body content</template>
  <template #footer="{ close }">
    <Button @click="close">Continue</Button>
  </template>
</Modal>

ui keys: backdrop, container, dialog, header, icon, heading, body, footer, closeTrigger.

Examples

Sizes

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'const sizes = ['xs', 'sm', 'md', 'lg', 'cover', 'full'] as const</script><template>  <div class="flex flex-wrap gap-4">    <Modal v-for="size in sizes" :key="size" :size="size">      <Button variant="secondary">        {{ size.charAt(0).toUpperCase() + size.slice(1) }}      </Button>      <template #icon>        <Icon class="text-foreground" icon="gravity-ui:rocket" />      </template>      <template #title>        Size: {{ size.charAt(0).toUpperCase() + size.slice(1) }}      </template>      <template #body>        <p v-if="size === 'cover'">          This modal uses the <code>cover</code> size variant. It spans the full          screen with margins: 16px on mobile and 40px on desktop. Maintains rounded          corners and standard padding. Perfect for cover-style content that needs          maximum width while preserving modal aesthetics.        </p>        <p v-else-if="size === 'full'">          This modal uses the <code>full</code> size variant. It occupies the entire          viewport without any margins, rounded corners, or shadows, creating a true          fullscreen experience. Ideal for immersive content or full-page          interactions.        </p>        <p v-else>          This modal uses the <code>{{ size }}</code> size variant. On mobile devices, all          sizes adapt to near full-width for optimal viewing. On desktop, each size          provides a different maximum width to suit various content needs.        </p>      </template>      <template #footer="{ close }">        <Button variant="secondary" @click="close">          Cancel        </Button>        <Button @click="close">          Confirm        </Button>      </template>    </Modal>  </div></template>

Placement

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'const placements = ['auto', 'top', 'center', 'bottom'] as const</script><template>  <div class="flex flex-wrap gap-4">    <Modal v-for="placement in placements" :key="placement" :placement="placement">      <Button variant="secondary">        {{ placement.charAt(0).toUpperCase() + placement.slice(1) }}      </Button>      <template #icon>        <Icon class="text-foreground" icon="gravity-ui:rocket" />      </template>      <template #title>        Placement: {{ placement.charAt(0).toUpperCase() + placement.slice(1) }}      </template>      <template #body>        <p>          This modal uses the <code>{{ placement }}</code> placement option. Try different          placements to see how the modal positions itself on the screen.        </p>      </template>      <template #footer="{ close }">        <Button class="w-full" @click="close">          Continue        </Button>      </template>    </Modal>  </div></template>

Scroll Behavior

<script setup lang="ts">import type { RadioGroupItem } from '@hareui/vue'import { Button, Modal, RadioGroup } from '@hareui/vue'import { ref } from 'vue'const scroll = ref<'inside' | 'outside'>('inside')const items: RadioGroupItem[] = [  { value: 'inside', label: 'Inside' },  { value: 'outside', label: 'Outside' },]</script><template>  <div class="flex flex-col gap-4">    <RadioGroup v-model="scroll" orientation="horizontal" name="scroll" :items="items" />    <Modal :scroll="scroll">      <Button variant="secondary">        Open Modal ({{ scroll.charAt(0).toUpperCase() + scroll.slice(1) }})      </Button>      <template #title>        Scroll: {{ scroll.charAt(0).toUpperCase() + scroll.slice(1) }}      </template>      <template #body>        <p v-for="i in 30" :key="i" class="mb-3">          Paragraph {{ i }}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.          Nullam pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet          hendrerit risus, sed porttitor quam.        </p>      </template>      <template #footer="{ close }">        <Button variant="secondary" @click="close">          Cancel        </Button>        <Button @click="close">          Confirm        </Button>      </template>    </Modal>  </div></template>

Controlled State

Controlled with v-model:open

Control the modal using Vue's ref and v-model:open for simple, external state management.

Status: closed

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'import { ref } from 'vue'const isOpen = ref(false)</script><template>  <div class="flex max-w-md flex-col gap-3">    <h3 class="text-lg font-semibold text-foreground">      Controlled with v-model:open    </h3>    <p class="text-sm leading-relaxed text-pretty text-muted">      Control the modal using Vue's <code class="text-foreground">ref</code> and      <code class="text-foreground">v-model:open</code> for simple, external state management.    </p>    <div class="flex flex-col items-start gap-3 rounded-2xl bg-surface p-4 shadow-sm">      <div class="flex w-full items-center justify-between">        <p class="text-xs text-muted">          Status:          <span class="font-mono font-medium text-foreground">{{ isOpen ? 'open' : 'closed' }}</span>        </p>      </div>      <div class="flex gap-2">        <Button size="sm" variant="secondary" @click="isOpen = true">          Open Modal        </Button>        <Button size="sm" variant="tertiary" @click="isOpen = !isOpen">          Toggle        </Button>      </div>    </div>    <Modal v-model:open="isOpen">      <template #icon>        <Icon class="text-accent-soft-foreground" icon="gravity-ui:circle-check" />      </template>      <template #title>        Controlled with v-model:open      </template>      <template #body>        <p>          This modal is controlled by Vue's <code>ref</code>. Bind          <code>v-model:open</code> to manage the modal state externally.        </p>      </template>      <template #footer="{ close }">        <Button variant="secondary" @click="close">          Cancel        </Button>        <Button @click="close">          Confirm        </Button>      </template>    </Modal>  </div></template>

With Form

<script setup lang="ts">import { Button, Input, Modal, Surface, TextField } from '@hareui/vue'import { Icon } from '@iconify/vue'const fields = [  { name: 'name', type: 'text', label: 'Name', placeholder: 'Enter your name' },  { name: 'email', type: 'email', label: 'Email', placeholder: 'Enter your email' },  { name: 'phone', type: 'tel', label: 'Phone', placeholder: 'Enter your phone number' },  { name: 'company', type: undefined, label: 'Company', placeholder: 'Enter your company name' },  { name: 'message', type: undefined, label: 'Message', placeholder: 'Enter your message' },]</script><template>  <Modal placement="auto" class="sm:max-w-md" :ui="{ icon: 'bg-accent-soft text-accent-soft-foreground', body: 'p-6' }">    <Button variant="secondary">      Open Contact Form    </Button>    <template #icon>      <Icon class="size-5" icon="gravity-ui:envelope" />    </template>    <template #title>      Contact Us    </template>    <template #description>      <p class="mt-1.5 text-sm leading-5 text-muted">        Fill out the form below and we'll get back to you. The modal adapts automatically        when the keyboard appears on mobile.      </p>    </template>    <template #body>      <Surface variant="default">        <form class="flex flex-col gap-4">          <TextField            v-for="field in fields"            :key="field.name"            class="w-full"            :name="field.name"            :type="field.type"            variant="secondary"            :label="field.label"          >            <Input :placeholder="field.placeholder" />          </TextField>        </form>      </Surface>    </template>    <template #footer="{ close }">      <Button variant="secondary" @click="close">        Cancel      </Button>      <Button @click="close">        Send Message      </Button>    </template>  </Modal></template>

Custom Trigger

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template>  <Modal class="sm:max-w-[360px]" :ui="{ icon: 'bg-accent-soft text-accent-soft-foreground' }">    <button      type="button"      class="group flex items-center gap-3 rounded-2xl bg-surface p-4 shadow-xs select-none hover:bg-surface-secondary"    >      <div class="flex size-12 shrink-0 items-center justify-center rounded-xl bg-accent-soft text-accent-soft-foreground">        <Icon class="size-6" icon="gravity-ui:gear" />      </div>      <div class="flex flex-1 flex-col gap-0.5 text-start">        <p class="text-sm font-semibold">          Settings        </p>        <p class="text-xs text-muted">          Manage your preferences        </p>      </div>    </button>    <template #icon>      <Icon class="size-5" icon="gravity-ui:gear" />    </template>    <template #title>      Settings    </template>    <template #body>      <p>        Use the <code>default</code> slot to create custom trigger elements beyond standard        buttons. This example shows a card-style trigger with icons and descriptive text.      </p>    </template>    <template #footer="{ close }">      <Button variant="secondary" @click="close">        Cancel      </Button>      <Button @click="close">        Save      </Button>    </template>  </Modal></template>

Backdrop Variants

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'const variants = ['opaque', 'blur', 'transparent'] as const</script><template>  <div class="flex flex-wrap gap-4">    <Modal v-for="variant in variants" :key="variant" :variant="variant">      <Button variant="secondary">        {{ variant.charAt(0).toUpperCase() + variant.slice(1) }}      </Button>      <template #icon>        <Icon class="text-foreground" icon="gravity-ui:rocket" />      </template>      <template #title>        Backdrop: {{ variant.charAt(0).toUpperCase() + variant.slice(1) }}      </template>      <template #body>        <p>          This modal uses the <code>{{ variant }}</code> backdrop variant. Compare the          different visual effects: opaque provides full opacity, blur adds a backdrop          filter, and transparent removes the background.        </p>      </template>      <template #footer="{ close }">        <Button class="w-full" @click="close">          Continue        </Button>      </template>    </Modal>  </div></template>

Custom Backdrop

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template>  <Modal    variant="blur"    class="sm:max-w-[360px]"    :ui="{      backdrop: 'bg-linear-to-t from-black/80 via-black/40 to-transparent dark:from-zinc-800/80 dark:via-zinc-800/40',      header: 'items-center text-center',      icon: 'bg-accent-soft text-accent-soft-foreground',      footer: 'flex-col-reverse',    }"  >    <Button variant="secondary">      Custom Backdrop    </Button>    <template #icon>      <Icon class="size-5" icon="gravity-ui:sparkles" />    </template>    <template #title>      Premium Backdrop    </template>    <template #body>      <p>        This backdrop features a sophisticated gradient that transitions from a dark color        at the bottom to complete transparency at the top, combined with a smooth blur        effect. The gradient automatically adapts its intensity for optimal contrast in both        light and dark modes.      </p>    </template>    <template #footer="{ close }">      <Button class="w-full" @click="close">        Amazing!      </Button>      <Button class="w-full" variant="secondary" @click="close">        Close      </Button>    </template>  </Modal></template>

Dismiss Behavior

dismissible

Controls whether the modal can be dismissed by clicking the backdrop or pressing Escape. Defaults to true. Set to false to require an explicit close action.

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template>  <div class="flex max-w-sm flex-col gap-6">    <div class="flex flex-col gap-2">      <h3 class="text-lg font-semibold">        dismissible      </h3>      <p class="text-sm text-muted">        Controls whether the modal can be dismissed by clicking the backdrop or pressing        Escape. Defaults to <code>true</code>. Set to <code>false</code> to require an        explicit close action.      </p>      <Modal :dismissible="false" class="sm:max-w-[360px]" :ui="{ icon: 'bg-default text-foreground' }">        <Button variant="secondary">          Open Modal        </Button>        <template #icon>          <Icon class="size-5" icon="gravity-ui:circle-info" />        </template>        <template #title>          dismissible = false        </template>        <template #description>          <p class="text-sm leading-5 text-muted">            Clicking the backdrop or pressing Escape won't close this modal          </p>        </template>        <template #body>          <p>            Try clicking outside this modal or pressing Escape — nothing happens. You must            use the close button to dismiss it.          </p>        </template>        <template #footer="{ close }">          <Button class="w-full" @click="close">            Close          </Button>        </template>      </Modal>    </div>  </div></template>

Close Methods

Using the footer slot's close()

The simplest way to close a modal. The #footer slot receives a close function — bind it to any Button's @click handler.

Using the #content slot's close()

Access the close function from the #content slot, which replaces the entire dialog body. This gives you full control over when and how to close the modal, allowing you to add custom logic before closing.

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template>  <div class="flex max-w-2xl flex-col gap-8">    <div class="flex flex-col gap-2">      <h3 class="text-lg font-semibold">        Using the footer slot's close()      </h3>      <p class="text-sm text-muted">        The simplest way to close a modal. The <code>#footer</code> slot receives a        <code>close</code> function — bind it to any Button's <code>@click</code> handler.      </p>      <Modal>        <Button variant="secondary">          Open Modal        </Button>        <template #icon>          <Icon class="text-accent-soft-foreground" icon="gravity-ui:circle-info" />        </template>        <template #title>          Using footer close()        </template>        <template #body>          <p>            Click either button below — both call the <code>close</code> function passed to the            <code>#footer</code> slot and will close the modal automatically.          </p>        </template>        <template #footer="{ close }">          <Button variant="secondary" @click="close">            Cancel          </Button>          <Button @click="close">            Confirm          </Button>        </template>      </Modal>    </div>    <div class="flex flex-col gap-2">      <h3 class="text-lg font-semibold">        Using the #content slot's close()      </h3>      <p class="text-sm text-muted">        Access the <code>close</code> function from the <code>#content</code> slot, which        replaces the entire dialog body. This gives you full control over when and how to        close the modal, allowing you to add custom logic before closing.      </p>      <Modal>        <Button variant="secondary">          Open Modal        </Button>        <template #content="{ close }">          <div class="flex flex-col gap-3">            <div class="flex items-center gap-3">              <div class="flex size-10 shrink-0 items-center justify-center rounded-3xl bg-success-soft text-success-soft-foreground">                <Icon icon="gravity-ui:circle-check" />              </div>              <p class="text-base font-medium text-foreground">                Using #content slot's close()              </p>            </div>            <p class="text-sm text-muted">              The buttons below use the <code>close</code> function from the              <code>#content</code> slot's scope. You can add validation or other logic              before calling <code>close()</code>.            </p>            <div class="mt-2 flex justify-end gap-2">              <Button variant="secondary" @click="close">                Cancel              </Button>              <Button @click="close">                Confirm              </Button>            </div>          </div>        </template>      </Modal>    </div>  </div></template>

Custom Animations

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'const animations = [  {    name: 'Kinematic Scale',    icon: 'gravity-ui:sparkles',    description:      'Physics-based elastic scaling. Simulates a high-damping spring system with fast transient response and prolonged settling time. Ideal for Modals and Popovers.',    classNames: {      backdrop: [        'data-[state=open]:duration-400',        'data-[state=open]:ease-[cubic-bezier(0.16,1,0.3,1)]',        'data-[state=closed]:duration-200',        'data-[state=closed]:ease-[cubic-bezier(0.7,0,0.84,0)]',      ].join(' '),      container: [        'data-[state=open]:animate-in',        'data-[state=open]:fade-in-0',        'data-[state=open]:zoom-in-95',        'data-[state=open]:duration-400',        'data-[state=open]:ease-[cubic-bezier(0.16,1,0.3,1)]',        'data-[state=closed]:animate-out',        'data-[state=closed]:fade-out-0',        'data-[state=closed]:zoom-out-95',        'data-[state=closed]:duration-200',        'data-[state=closed]:ease-[cubic-bezier(0.7,0,0.84,0)]',      ].join(' '),    },  },  {    name: 'Fluid Slide',    icon: 'gravity-ui:arrow-up-from-line',    description:      'Simulates movement through a medium with fluid resistance. Eliminates mechanical linearity for a natural, grounded feel. Perfect for Bottom Sheets or Toasts.',    classNames: {      backdrop: [        'data-[state=open]:duration-500',        'data-[state=open]:ease-[cubic-bezier(0.25,1,0.5,1)]',        'data-[state=closed]:duration-200',        'data-[state=closed]:ease-[cubic-bezier(0.5,0,0.75,0)]',      ].join(' '),      container: [        'data-[state=open]:animate-in',        'data-[state=open]:fade-in-0',        'data-[state=open]:slide-in-from-bottom-4',        'data-[state=open]:duration-500',        'data-[state=open]:ease-[cubic-bezier(0.25,1,0.5,1)]',        'data-[state=closed]:animate-out',        'data-[state=closed]:fade-out-0',        'data-[state=closed]:slide-out-to-bottom-2',        'data-[state=closed]:duration-200',        'data-[state=closed]:ease-[cubic-bezier(0.5,0,0.75,0)]',      ].join(' '),    },  },]</script><template>  <div class="flex flex-wrap gap-4">    <Modal      v-for="animation in animations"      :key="animation.name"      :ui="{ backdrop: animation.classNames.backdrop, container: animation.classNames.container, dialog: 'sm:max-w-[360px]', icon: 'bg-default text-foreground' }"    >      <Button variant="secondary">        {{ animation.name }}      </Button>      <template #icon>        <Icon class="size-5" :icon="animation.icon" />      </template>      <template #title>        {{ animation.name }} Animation      </template>      <template #body>        <p class="mt-1">          {{ animation.description }}        </p>      </template>      <template #footer="{ close }">        <Button variant="tertiary" @click="close">          Close        </Button>        <Button @click="close">          Try Again        </Button>      </template>    </Modal>  </div></template>

Custom Portal

Render modals inside a custom container instead of document.body

Apply transform: translateZ(0) to the container to create a new stacking context.

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { ref } from 'vue'const portalRef = ref<HTMLElement>()</script><template>  <div class="flex flex-col gap-4">    <div>      <p class="text-sm">        Render modals inside a custom container instead of <code>document.body</code>      </p>      <p class="text-sm text-muted">        Apply <code class="rounded px-1 py-0.5 text-xs">transform: translateZ(0)</code> to the        container to create a new stacking context.      </p>    </div>    <div      ref="portalRef"      class="relative flex h-[380px] items-center justify-center overflow-hidden rounded bg-muted/20"      style="transform: translate(0)"    >      <Modal v-if="portalRef" :portal="portalRef" :ui="{ backdrop: 'h-full' }">        <Button>Open Modal</Button>        <template #title>          Custom Portal        </template>        <template #body>          <p class="text-sm text-muted">            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis            nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.          </p>          <p class="text-sm text-muted">            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis            nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.          </p>          <p class="text-sm text-muted">            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis            nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.          </p>        </template>        <template #footer="{ close }">          <Button variant="secondary" @click="close">            Close          </Button>        </template>      </Modal>    </div>  </div></template>

Customization

Tailwind CSS

<script setup lang="ts">import { Button, Modal } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template>  <Modal    variant="blur"    :ui="{      backdrop: 'bg-overlay/50 dark:bg-overlay/65',      dialog: 'relative overflow-hidden border border-border/80 bg-surface/90 shadow-2xl ring-1 ring-black/5 backdrop-blur-xl sm:max-w-[340px] dark:border-border/90 dark:bg-surface/85 dark:ring-white/10',    }"  >    <Button variant="secondary">      Open    </Button>    <template #icon>      <Icon class="text-neutral-700 dark:text-neutral-200" icon="gravity-ui:circle-check" />    </template>    <template #title>      Changes saved    </template>    <template #body>      <p class="text-sm text-muted">        Your draft is synced across devices.      </p>    </template>    <template #footer="{ close }">      <Button class="w-full" @click="close">        Done      </Button>    </template>  </Modal></template>

Global theme override

app.use(createHareUI({
  ui: {
    modal: {
      slots: { dialog: 'rounded-2xl' },
    },
  },
}))

Styling Reference

ui keydata-slotElement
backdropmodal-backdropThe full-screen overlay behind the dialog (Reka DialogOverlay)
containermodal-containerPositioning wrapper with placement support
dialogmodal-dialogThe dialog surface (Reka DialogContent); class merges here
headermodal-headerHeader section for the icon, title and description, rendered when #header, #icon, #description, or a title is present
iconmodal-iconIcon wrapper rendered before the title, rendered when #icon is present
headingmodal-headingThe title (Reka DialogTitle)
bodymodal-bodyThe scrollable content area, rendered when #body is present
footermodal-footerFooter section for actions, rendered when #footer is present
closeTriggermodal-close-triggerThe default close button, rendered when closeButton is true

Interactive States

  • data-[state=open] / data-[state=closed] — applied to backdrop and container during the enter/exit animation, mapped from HeroUI's [data-entering] / [data-exiting]
  • data-placement="auto|top|center|bottom" — applied to container and dialog, mirroring the placement prop

API Reference

Props

PropTypeDefaultDescription
openbooleanundefinedThe controlled open state of the modal. Use with v-model:open.
defaultOpenboolean-The open state of the modal when it is initially rendered. Use when you do not need to control its open state.
titlestring-Title rendered in the header.
scroll"inside" | "outside"'inside'Where the dialog scrolls when its content overflows the viewport.
size"md" | "xs" | "sm" | "lg" | "cover" | "full"'md'Size of the dialog.
variant"opaque" | "blur" | "transparent"'opaque'Visual style of the backdrop.
placement"auto" | "top" | "center" | "bottom""auto"Vertical position of the dialog within the viewport.
dismissiblebooleantrueWhether clicking the backdrop or pressing Escape closes the modal.
closeButtonbooleantrueWhether the default close button is rendered.
portalstring | boolean | HTMLElementtrueWhere the modal is teleported. true teleports to body, false renders in place, a string or element teleports to that target.
uiComponentSlots<{ slots: { backdrop: string[]; container: string[]; dialog: string[]; header: string; heading: string; icon: string; body: string[]; footer: string[]; closeTrigger: string; }; variants: { scroll: { inside: { dialog: string; body: string; }; outside: { backdrop: string; container: string; dialog: string; body: string; }; }; variant: { opaque: { backdrop: string; }; blur: { backdrop: string; }; transparent: { backdrop: string; }; }; placement: { auto: { container: string; dialog: string; }; top: { container: string; dialog: string; }; center: { container: string; dialog: string; }; bottom: { container: string; dialog: string; }; }; size: { xs: { dialog: string; }; sm: { dialog: string; }; md: { dialog: string; }; lg: { dialog: string; }; cover: { dialog: string; }; full: { container: string[]; dialog: string; }; }; }; defaultVariants: { scroll: string; size: string; variant: string; placement: string; }; }>-Per-slot class overrides.

Slots

SlotPropsDescription
default{ open: boolean; }The trigger element. Omit to control the modal purely via v-model:open.
headeranyReplaces the whole header (icon and title).
iconanyIcon rendered before the title in the header.
titleanyReplaces the title text.
descriptionanySupporting text after the title, linked to the dialog as its description. Pass one element (e.g. a <p>).
body{ close: () => void; }The dialog body.
footer{ close: () => void; }The dialog footer (actions).
close{ close: () => void; }Replaces the default close button.
content{ close: () => void; }Replaces the entire dialog content (header, body, footer, close trigger).

Emits

EventPayloadDescription
update:open[value: boolean]-

Accessibility

Implements the WAI-ARIA Dialog pattern via Reka UI's Dialog primitive:

  • Focus trap: focus is locked within the dialog while open and restored to the trigger on close
  • Keyboard: Escape closes the dialog (unless dismissible is false), Tab cycles through focusable elements inside it
  • Screen readers: the dialog always has an accessible name — either the rendered title or a visually hidden one
  • Scroll lock: body scroll is disabled while the modal is open

On this page

No Headings