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 keydata-slotElement
backdropalert-dialog-backdropOverlay backdrop behind the dialog (Reka DialogOverlay)
containeralert-dialog-containerPositioning wrapper with placement support
dialogalert-dialog-dialogDialog content container (Reka DialogContent, role="alertdialog"); class merges here
headeralert-dialog-headerHeader section for the icon and title
headingalert-dialog-headingHeading text (Reka DialogTitle)
bodyalert-dialog-bodyMain content area, rendered when #body is present
footeralert-dialog-footerFooter section for actions, rendered when #footer is present
iconalert-dialog-iconIcon container with status colors, rendered when icon is true
closeTriggeralert-dialog-close-triggerClose 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 effect
  • variant="transparent" - Transparent backdrop (no overlay)

Status Variants (Icon)

  • status="default" - Default gray status
  • status="accent" - Accent blue status
  • status="success" - Success green status
  • status="warning" - Warning orange status
  • status="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] on backdrop and container during the enter/exit animation, mapped from HeroUI's [data-entering] / [data-exiting]
  • Placement: data-placement="auto|top|center|bottom" on container and dialog, mirroring the placement prop

API Reference

Props

PropTypeDefaultDescription
openbooleanundefinedThe controlled open state of the alert dialog. Use with v-model:open.
defaultOpenboolean-The open state of the alert dialog when it is initially rendered. Use when you do not need to control its open state.
titlestring-Title rendered in the header.
status"default" | "accent" | "success" | "warning" | "danger"'danger'Status color of the header icon. Also picks the default icon.
iconbooleantrueWhether 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.
dismissiblebooleanfalseWhether clicking the backdrop closes the alert dialog. Alert dialogs require an explicit action by default.
keyboardDismissiblebooleanfalseWhether pressing Escape closes the alert dialog.
closeButtonbooleantrueWhether the default close button is rendered.
portalstring | boolean | HTMLElementtrueWhere the alert dialog 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: { 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

SlotPropsDescription
default{ open: boolean; }The trigger element. Omit to control the alert dialog purely via v-model:open.
headeranyReplaces the whole header (icon and title).
iconanyReplaces the default status icon inside the icon wrapper.
titleanyReplaces 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

EventPayloadDescription
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: Escape closes it only when keyboardDismissible is true, Tab cycles 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 Escape dismisses it (dismissible and keyboardDismissible are false)

On this page

No Headings