AlertDialog
Modal dialog for critical confirmations requiring user attention and explicit action
Usage
import { AlertDialog } from '@hareui/vue'<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'</script><template> <AlertDialog class="sm:max-w-[400px]" status="danger" title="Delete project permanently?"> <Button variant="danger"> Delete Project </Button> <template #body> <p> This will permanently delete <strong>My Awesome Project</strong> and all of its data. This action cannot be undone. </p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> Cancel </Button> <Button variant="danger" @click="close"> Delete Project </Button> </template> </AlertDialog></template>Anatomy
A single AlertDialog 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 props and named slots.
<AlertDialog status="danger" title="Heading">
<Button>Open Alert Dialog</Button>
<template #icon><!-- optional: replaces the status icon --></template>
<template #body>Body content</template>
<template #footer="{ close }">
<Button variant="tertiary" @click="close">Cancel</Button>
<Button @click="close">Confirm</Button>
</template>
</AlertDialog>ui keys: backdrop, container, dialog, header, icon, heading, body, footer, closeTrigger.
Examples
Statuses
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'const examples = [ { actions: { cancel: 'Stay Signed In', confirm: 'Sign Out' }, body: 'You\'ll need to sign in again to access your account. Any unsaved changes will be lost.', classNames: 'bg-accent-soft text-accent-soft-foreground', header: 'Sign out of your account?', status: 'accent', trigger: 'Sign Out', }, { actions: { cancel: 'Not Yet', confirm: 'Mark Complete' }, body: 'This will mark the task as complete and notify all team members. The task will be moved to your completed list.', classNames: 'bg-success-soft text-success-soft-foreground', header: 'Complete this task?', status: 'success', trigger: 'Complete Task', }, { actions: { cancel: 'Keep Editing', confirm: 'Discard' }, body: 'You have unsaved changes that will be permanently lost. Are you sure you want to discard them?', classNames: 'bg-warning-soft text-warning-soft-foreground', header: 'Discard unsaved changes?', status: 'warning', trigger: 'Discard Changes', }, { actions: { cancel: 'Cancel', confirm: 'Delete Account' }, body: 'This will permanently delete your account and remove all your data from our servers. This action is irreversible.', classNames: 'bg-danger-soft text-danger-soft-foreground', header: 'Delete your account?', status: 'danger', trigger: 'Delete Account', },] as const</script><template> <div class="flex flex-wrap gap-4"> <AlertDialog v-for="example in examples" :key="example.status" class="sm:max-w-[400px]" :status="example.status" :title="example.header" > <Button :class="example.classNames"> {{ example.trigger }} </Button> <template #body> <p>{{ example.body }}</p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> {{ example.actions.cancel }} </Button> <Button :variant="example.status === 'danger' ? 'danger' : 'primary'" @click="close"> {{ example.actions.confirm }} </Button> </template> </AlertDialog> </div></template>Placements
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'const placements = ['auto', 'top', 'center', 'bottom'] as constconst capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)</script><template> <div class="flex flex-wrap gap-4"> <AlertDialog v-for="placement in placements" :key="placement" class="sm:max-w-[400px]" :placement="placement" status="accent" :title="placement === 'auto' ? 'Auto Placement' : `${capitalize(placement)} Position`" > <Button variant="secondary"> {{ capitalize(placement) }} </Button> <template #body> <p> {{ placement === 'auto' ? 'Automatically positions at the bottom on mobile and center on desktop for optimal user experience.' : `This dialog is positioned at the ${placement} of the viewport. Critical confirmations are typically centered for maximum attention.` }} </p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Confirm </Button> </template> </AlertDialog> </div></template>Sizes
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'import { Icon } from '@iconify/vue'const sizes = ['xs', 'sm', 'md', 'lg', 'cover'] as constconst capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)</script><template> <div class="flex flex-wrap gap-4"> <AlertDialog v-for="size in sizes" :key="size" :size="size" :title="`Size: ${capitalize(size)}`" :ui="{ icon: 'bg-default text-foreground' }" > <Button variant="secondary"> {{ capitalize(size) }} </Button> <template #icon> <Icon class="size-5" icon="gravity-ui:rocket" /> </template> <template #body> <p v-if="size === 'cover'"> This alert dialog 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 critical confirmations that need maximum width while preserving alert dialog aesthetics. </p> <p v-else> This alert dialog 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="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Confirm </Button> </template> </AlertDialog> </div></template>Controlled State
With v-model:open
Control the alert dialog using Vue's ref and v-model:open for simple state management. Perfect for basic use cases.
Status: closed
With useOverlayState()
Use the useOverlayState composable for a cleaner API with methods like open(), close(), and toggle().
Status: closed
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'import { reactive, ref } from 'vue'const isOpen = ref(false)// Vue counterpart of HeroUI's `useOverlayState` hook: a ref plus named open/close/toggle helpers.function useOverlayState(defaultOpen = false) { const isOpen = ref(defaultOpen) return reactive({ isOpen, open: () => { isOpen.value = true }, close: () => { isOpen.value = false }, toggle: () => { isOpen.value = !isOpen.value }, setOpen: (value: boolean) => { isOpen.value = value }, })}const state = useOverlayState()</script><template> <div class="flex max-w-md flex-col gap-8"> <div class="flex flex-col gap-3"> <h3 class="text-lg font-semibold text-foreground"> With v-model:open </h3> <p class="text-sm leading-relaxed text-pretty text-muted"> Control the alert dialog using Vue's <code class="text-foreground">ref</code> and <code class="text-foreground">v-model:open</code> for simple state management. Perfect for basic use cases. </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 Dialog </Button> <Button size="sm" variant="tertiary" @click="isOpen = !isOpen"> Toggle </Button> </div> </div> <AlertDialog v-model:open="isOpen" class="sm:max-w-[400px]" status="accent" title="Controlled with v-model:open"> <template #body> <p> This alert dialog is controlled by Vue's <code>ref</code>. Bind <code>v-model:open</code> to manage the dialog state externally. </p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Confirm </Button> </template> </AlertDialog> </div> <div class="flex flex-col gap-3"> <h3 class="text-lg font-semibold text-foreground"> With useOverlayState() </h3> <p class="text-sm leading-relaxed text-pretty text-muted"> Use the <code class="text-foreground">useOverlayState</code> composable for a cleaner API with methods like <code>open()</code>, <code>close()</code>, and <code>toggle()</code>. </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">{{ state.isOpen ? 'open' : 'closed' }}</span> </p> </div> <div class="flex gap-2"> <Button size="sm" variant="secondary" @click="state.open"> Open Dialog </Button> <Button size="sm" variant="tertiary" @click="state.toggle"> Toggle </Button> </div> </div> <AlertDialog :open="state.isOpen" class="sm:max-w-[400px]" status="success" title="Controlled with useOverlayState()" @update:open="state.setOpen"> <template #body> <p> The <code>useOverlayState</code> composable provides dedicated methods for common operations. No need to manually create callbacks—just use <code>state.open()</code>, <code>state.close()</code>, or <code>state.toggle()</code>. </p> </template> <template #footer> <Button variant="tertiary" @click="state.close"> Cancel </Button> <Button @click="state.close"> Confirm </Button> </template> </AlertDialog> </div> </div></template>Custom Icon
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template> <AlertDialog class="sm:max-w-[400px]" status="warning" title="Reset your password?"> <Button variant="secondary"> Reset Password </Button> <template #icon> <Icon class="size-5" icon="gravity-ui:lock-open" /> </template> <template #body> <p> We'll send a password reset link to your email address. You'll need to create a new password to regain access to your account. </p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Send Reset Link </Button> </template> </AlertDialog></template>Custom Trigger
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template> <AlertDialog class="sm:max-w-[400px]" status="danger" title="Delete this item?"> <!-- Any focusable element can be the trigger (HeroUI's AlertDialog.Trigger) --> <button type="button" class="group flex cursor-(--cursor-interactive) items-center gap-3 rounded-2xl bg-surface p-4 text-start shadow-xs transition-[transform,background-color,box-shadow] select-none hover:bg-surface-secondary focus-visible:status-focused active:scale-[0.97] motion-reduce:transition-none" > <div class="flex size-12 shrink-0 items-center justify-center rounded-xl bg-danger-soft text-danger-soft-foreground"> <Icon class="size-6" icon="gravity-ui:trash-bin" /> </div> <div class="flex flex-1 flex-col gap-0.5"> <p class="text-sm font-semibold"> Delete Item </p> <p class="text-xs text-muted"> Permanently remove this item </p> </div> </button> <template #icon> <Icon class="size-5" icon="gravity-ui:trash-bin" /> </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="tertiary" @click="close"> Cancel </Button> <Button variant="danger" @click="close"> Delete Item </Button> </template> </AlertDialog></template>Backdrop Variants
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'const variants = ['opaque', 'blur', 'transparent'] as constconst capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)const descriptions = { opaque: 'An opaque dark backdrop that completely obscures the background, providing maximum focus on the dialog.', blur: 'A blurred backdrop that softly obscures the background while maintaining visual context.', transparent: 'A transparent backdrop that keeps the background fully visible, useful for less critical confirmations.',}</script><template> <div class="flex flex-wrap gap-4"> <AlertDialog v-for="variant in variants" :key="variant" class="sm:max-w-[400px]" status="accent" :title="`Backdrop: ${capitalize(variant)}`" :variant="variant" > <Button variant="secondary"> {{ capitalize(variant) }} </Button> <template #body> <p>{{ descriptions[variant] }}</p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Confirm </Button> </template> </AlertDialog> </div></template>Custom Backdrop
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template> <AlertDialog class="sm:max-w-[420px]" status="danger" title="Permanently delete your account?" variant="blur" :ui="{ backdrop: 'bg-linear-to-t from-red-950/90 via-red-950/50 to-transparent dark:from-red-950/95 dark:via-red-950/60', header: 'items-center text-center', footer: 'flex-col-reverse', }" > <Button variant="danger"> Delete Account </Button> <template #icon> <Icon class="size-5" icon="gravity-ui:triangle-exclamation" /> </template> <template #body> <p> This action cannot be undone. All your data, settings, and content will be permanently removed from our servers. The dramatic red backdrop emphasizes the severity and irreversibility of this decision. </p> </template> <template #footer="{ close }"> <Button class="w-full" @click="close"> Keep Account </Button> <Button class="w-full" variant="danger" @click="close"> Delete Forever </Button> </template> </AlertDialog></template>Dismiss Behavior
dismissible
Controls whether the alert dialog can be dismissed by clicking the overlay backdrop. Alert dialogs typically require explicit action, so this defaults to false. Set to true for less critical confirmations.
keyboardDismissible
Controls whether the ESC key can dismiss the alert dialog. Alert dialogs typically require explicit action, so this defaults to false. When set to true, the ESC key will be enabled.
<script setup lang="ts">import { AlertDialog, Button } 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 alert dialog can be dismissed by clicking the overlay backdrop. Alert dialogs typically require explicit action, so this defaults to <code>false</code>. Set to <code>true</code> for less critical confirmations. </p> <AlertDialog class="sm:max-w-[400px]" :dismissible="false"> <Button variant="secondary"> Open Alert Dialog </Button> <template #header> <div data-slot="alert-dialog-icon" class="flex size-10 shrink-0 items-center justify-center rounded-3xl select-none bg-danger-soft text-danger-soft-foreground"> <Icon class="size-5" icon="gravity-ui:circle-info" /> </div> <p class="align-middle text-base font-medium text-foreground"> dismissible = false </p> <p class="text-sm leading-5 text-muted"> Clicking the backdrop won't close this alert dialog </p> </template> <template #body> <p> Try clicking outside this alert dialog on the overlay - it won't close. You must use the action buttons to dismiss it. </p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Confirm </Button> </template> </AlertDialog> </div> <div class="flex flex-col gap-2"> <h3 class="text-lg font-semibold"> keyboardDismissible </h3> <p class="text-sm text-muted"> Controls whether the ESC key can dismiss the alert dialog. Alert dialogs typically require explicit action, so this defaults to <code>false</code>. When set to <code>true</code>, the ESC key will be enabled. </p> <AlertDialog class="sm:max-w-[400px]" :keyboard-dismissible="false"> <Button variant="secondary"> Open Alert Dialog </Button> <template #header> <div data-slot="alert-dialog-icon" class="flex size-10 shrink-0 items-center justify-center rounded-3xl select-none bg-accent-soft text-accent-soft-foreground"> <Icon class="size-5" icon="gravity-ui:circle-info" /> </div> <p class="align-middle text-base font-medium text-foreground"> keyboardDismissible = false </p> <p class="text-sm leading-5 text-muted"> ESC key is disabled </p> </template> <template #body> <p> Press ESC - nothing happens. You must use the action buttons to dismiss this alert dialog. </p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Confirm </Button> </template> </AlertDialog> </div> </div></template>Close Methods
Using the footer slot's close()
The simplest way to close a dialog. The #footer slot receives a close function — bind it to any Button's @click handler. When clicked, it will automatically close the dialog.
Using the #content slot's close()
Access the close function from the #content slot, which replaces the entire dialog content. This gives you full control over when and how to close the dialog, allowing you to add custom logic before closing.
<script setup lang="ts">import { AlertDialog, Button } 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 dialog. The <code>#footer</code> slot receives a <code>close</code> function — bind it to any Button's <code>@click</code> handler. When clicked, it will automatically close the dialog. </p> <AlertDialog class="sm:max-w-[400px]" :close-button="false" status="accent" title="Using footer close()"> <Button variant="secondary"> Open Dialog </Button> <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 dialog automatically. </p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Confirm </Button> </template> </AlertDialog> </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 content. This gives you full control over when and how to close the dialog, allowing you to add custom logic before closing. </p> <AlertDialog class="sm:max-w-[400px]"> <Button variant="secondary"> Open Dialog </Button> <template #content="{ close }"> <div class="flex flex-col gap-3"> <div class="flex size-10 shrink-0 items-center justify-center rounded-3xl bg-success-soft text-success-soft-foreground select-none"> <Icon class="size-5" icon="gravity-ui:circle-check" /> </div> <p class="align-middle text-base font-medium text-foreground"> Using #content slot's close() </p> </div> <div class="mt-2 text-sm leading-[1.43] text-muted"> <p> 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> <div class="mt-5 flex flex-row items-center justify-end gap-2"> <Button variant="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Confirm </Button> </div> </template> </AlertDialog> </div> </div></template>Custom Animations
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'import { Icon } from '@iconify/vue'// HeroUI's `data-[entering]` / `data-[exiting]` map to Reka's `data-[state=open]` / `data-[state=closed]`.const animations = [ { 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(' '), }, description: 'Physics-based elastic scaling. Simulates a high-damping spring system with fast transient response and prolonged settling time. Ideal for Alert Dialogs and Modals.', icon: 'gravity-ui:sparkles', name: 'Kinematic Scale', }, { 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(' '), }, description: 'Simulates movement through a medium with fluid resistance. Eliminates mechanical linearity for a natural, grounded feel. Perfect for Bottom Sheets or Toasts.', icon: 'gravity-ui:arrow-up-from-line', name: 'Fluid Slide', },]</script><template> <div class="flex flex-wrap gap-4"> <AlertDialog v-for="animation in animations" :key="animation.name" class="sm:max-w-[400px]" status="accent" :title="`${animation.name} Animation`" :ui="{ backdrop: animation.classNames.backdrop, container: animation.classNames.container }" > <Button variant="secondary"> {{ animation.name }} </Button> <template #icon> <Icon class="size-5" :icon="animation.icon" /> </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> </AlertDialog> </div></template>Custom Portal
Render alert dialogs 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 { AlertDialog, Button } 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 alert dialogs 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)" > <AlertDialog v-if="portalRef" class="h-full max-h-full sm:max-w-md" :portal="portalRef" status="accent" title="Custom Portal" :ui="{ backdrop: 'h-full', container: 'h-full max-h-full' }" > <Button>Open Alert Dialog</Button> <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="tertiary" @click="close"> Cancel </Button> <Button @click="close"> Confirm </Button> </template> </AlertDialog> </div> </div></template>Customization
Tailwind CSS
<script setup lang="ts">import { AlertDialog, Button } from '@hareui/vue'// HeroUI draws the top glow and hairline with two absolutely positioned `aria-hidden` divs in the// dialog; the props API has no slot before the header, so they are the dialog's ::before/::after.const dialogClass = [ 'relative overflow-hidden border border-border/80 bg-surface shadow-2xl ring-1 ring-accent/10 sm:max-w-[400px] dark:border-border/90 dark:bg-surface dark:ring-accent/15', 'before:pointer-events-none before:absolute before:inset-x-0 before:top-0 before:h-24 before:bg-linear-to-b before:from-accent/6 before:to-transparent dark:before:from-accent/10', 'after:pointer-events-none after:absolute after:inset-x-8 after:top-0 after:h-px after:bg-linear-to-r after:from-transparent after:via-accent/35 after:to-transparent dark:after:via-accent/45',].join(' ')</script><template> <AlertDialog :class="dialogClass" :close-button="false" status="accent" title="Sign out of your account?" variant="blur" :ui="{ backdrop: 'bg-overlay/50 dark:bg-overlay/60', header: 'relative', body: 'relative' }" > <Button variant="secondary"> Sign out </Button> <template #body> <p class="text-muted"> You will be signed out on this device. Unsaved work in <strong class="text-foreground">Acme Workspace</strong> may be lost unless it was saved to the cloud. </p> </template> <template #footer="{ close }"> <Button variant="tertiary" @click="close"> Stay signed in </Button> <Button @click="close"> Sign out </Button> </template> </AlertDialog></template>Global Configuration
To customize the AlertDialog classes for the whole app, extend the alertDialog theme in createHareUI:
app.use(createHareUI({
ui: {
alertDialog: {
slots: {
backdrop: 'bg-gradient-to-br from-black/60 to-black/80',
dialog: 'rounded-2xl border border-red-500/20 shadow-2xl',
header: 'gap-4',
icon: 'size-16',
closeTrigger: 'rounded-full bg-white/10 hover:bg-white/20',
},
},
},
}))Styling Reference
ui key | data-slot | Element |
|---|---|---|
backdrop | alert-dialog-backdrop | Overlay backdrop behind the dialog (Reka DialogOverlay) |
container | alert-dialog-container | Positioning wrapper with placement support |
dialog | alert-dialog-dialog | Dialog content container (Reka DialogContent, role="alertdialog"); class merges here |
header | alert-dialog-header | Header section for the icon and title |
heading | alert-dialog-heading | Heading text (Reka DialogTitle) |
body | alert-dialog-body | Main content area, rendered when #body is present |
footer | alert-dialog-footer | Footer section for actions, rendered when #footer is present |
icon | alert-dialog-icon | Icon container with status colors, rendered when icon is true |
closeTrigger | alert-dialog-close-trigger | Close button, rendered when closeButton is true |
The default status icon inside icon carries data-slot="alert-dialog-default-icon".
Backdrop Variants
variant="opaque"- Opaque colored backdrop (default)variant="blur"- Blurred backdrop with glass effectvariant="transparent"- Transparent backdrop (no overlay)
Status Variants (Icon)
status="default"- Default gray statusstatus="accent"- Accent blue statusstatus="success"- Success green statusstatus="warning"- Warning orange statusstatus="danger"- Danger red status (default)
Interactive States
- Focus:
:focus-visible- applied to the trigger, dialog, and close button - Hover:
:hover- applied to the close button on hover - Active:
:active- applied to the close button when pressed - Entering / Exiting:
data-[state=open]/data-[state=closed]onbackdropandcontainerduring the enter/exit animation, mapped from HeroUI's[data-entering]/[data-exiting] - Placement:
data-placement="auto|top|center|bottom"oncontaineranddialog, mirroring theplacementprop
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | undefined | The controlled open state of the alert dialog. Use with v-model:open. |
defaultOpen | boolean | - | The open state of the alert dialog when it is initially rendered. Use when you do not need to control its open state. |
title | string | - | Title rendered in the header. |
status | "default" | "accent" | "success" | "warning" | "danger" | 'danger' | Status color of the header icon. Also picks the default icon. |
icon | boolean | true | Whether the status icon is rendered in the header. The #icon slot replaces its content. |
size | "md" | "xs" | "sm" | "lg" | "cover" | '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. |
dismissible | boolean | false | Whether clicking the backdrop closes the alert dialog. Alert dialogs require an explicit action by default. |
keyboardDismissible | boolean | false | Whether pressing Escape closes the alert dialog. |
closeButton | boolean | true | Whether the default close button is rendered. |
portal | string | boolean | HTMLElement | true | Where the alert dialog is teleported. true teleports to body, false renders in place,
a string or element teleports to that target. |
ui | ComponentSlots<{ slots: { backdrop: string[]; container: string[]; dialog: string[]; header: string; heading: string; icon: string[]; body: string[]; footer: string[]; closeTrigger: string; }; variants: { status: { default: { icon: string; }; accent: { icon: string; }; success: { icon: string; }; warning: { icon: string; }; danger: { icon: 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; }; }; }; defaultVariants: { status: string; size: string; variant: string; placement: string; }; }> | - | Per-slot class overrides. |
Slots
| Slot | Props | Description |
|---|---|---|
default | { open: boolean; } | The trigger element. Omit to control the alert dialog purely via v-model:open. |
header | any | Replaces the whole header (icon and title). |
icon | any | Replaces the default status icon inside the icon wrapper. |
title | any | Replaces the title text. |
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
| Event | Payload | Description |
|---|---|---|
update:open | [value: boolean] | - |
Accessibility
Implements the WAI-ARIA AlertDialog pattern via Reka UI's Dialog primitive:
- Focus trap: focus is locked within the alert dialog
- Keyboard:
Escapecloses it only whenkeyboardDismissibleistrue,Tabcycles elements - Screen readers: the dialog has
role="alertdialog"and always has an accessible name, either the rendered title or a visually hidden one - Scroll lock: body scroll is disabled while open
- Required action: by default neither the backdrop nor
Escapedismisses it (dismissibleandkeyboardDismissiblearefalse)



