Alert Dialog
A modal dialog that interrupts the user with important content and expects a response.
Examples
Basic
| Prop | Default | Type | Description | 
|---|---|---|---|
| title | - | string | The title of the dialog. | 
| description | - | string | The description of the dialog. | 
| defaultOpen | false | boolean | The open state of the dialog when it is initially rendered. Use when you do not need to control its open state. | 
| open | - | boolean | The controlled open state of the dialog. Can be bind as v-model:open. | 
| overlay | true | boolean | Show the overlay. | 
| @action | - | event | Event emitted when the action button is clicked, typically used for confirming destructive actions. | 
| @cancel | - | event | Event emitted when the cancel button is clicked or dialog is dismissed, used for aborting the proposed action. | 
 Preview
 Code
<template>
  <NAlertDialog
    title="Are you absolutely sure?"
    description="This action cannot be undone. This will permanently delete your account and remove your data from our servers."
    @action="() => {
      console.log('action')
    }"
    @cancel="() => {
      console.log('cancel')
    }"
  >
    <template #trigger>
      <NButton btn="solid-gray">
        Show Dialog
      </NButton>
    </template>
  </NAlertDialog>
</template>
 Read more in Reka Alert Dialog Root API
Buttons
| Prop | Default | Type | Description | 
|---|---|---|---|
| _alertDialogCancel | { btn: 'solid-gray', label: 'Cancel' | AlertDialogCancelProps | The cancel button props. | 
| _alertDialogAction | { btn: 'soft-primary', label: 'Continue' } | AlertDialogActionProps | The action button props. | 
 Preview
 Code
<template>
  <NAlertDialog
    title="Are you absolutely sure?"
    description="This action cannot be undone. This will permanently delete your account and remove your data from our servers."
    :_alert-dialog-cancel="{
      btn: 'solid-white',
      label: 'Nevermind',
      leading: 'i-lucide-corner-up-left',
      onClick: () => {
        console.log('cancel')
      },
    }"
    :_alert-dialog-action="{
      btn: 'soft-red',
      label: 'Delete',
      leading: 'i-lucide-trash',
      onClick: () => {
        console.log('action')
      },
    }"
  >
    <template #trigger>
      <NButton btn="solid-gray">
        Show Dialog
      </NButton>
    </template>
  </NAlertDialog>
</template>
 Read more in Button component
Prevent Closing
| Prop | Default | Type | Description | 
|---|---|---|---|
| preventClose | - | boolean | If true, the alert dialog will not close escape key press. | 
 Preview
 Code
<template>
  <NAlertDialog
    title="Prevent Close Alert Dialog"
    description="This is alert dialog prevents closing using escape key."
    prevent-close
  >
    <template #trigger>
      <NButton btn="solid-gray">
        Open Dialog
      </NButton>
    </template>
  </NAlertDialog>
</template>
Slots
| Name | Props | Description | 
|---|---|---|
| default | - | Allows advanced customization using sub-components, replacing the default structure. | 
| content | - | Replaces the entire default content container within the dialog popup. | 
| trigger | open | The trigger button used to open the dialog. | 
| cancel | - | Custom content for the cancel button that aborts the proposed action. | 
| action | - | Custom content for the action button that confirms the destructive action. | 
| cancel-wrapper | - | Override the entire default cancel button. | 
| action-wrapper | - | Override the entire default action button. | 
| header | - | Custom content for the header section containing title and description. | 
| footer | - | Custom content for the footer section containing action and cancel buttons. | 
| title | - | Custom content for the dialog title, replacing the default title prop. | 
| description | - | Custom content for the dialog description, replacing the default description prop. | 
Custom Rendering
Use the default slot for full control over the alert dialog's structure. This allows you to compose the alert dialog using its individual sub-components (like AlertDialogAction, AlertDialogCancel, etc., listed in the Components section), similar to libraries like shadcn/ui.
 Preview
 Code
<template>
  <NAlertDialog>
    <NAlertDialogTrigger as-child>
      <NButton btn="solid-gray">
        Show Dialog
      </NButton>
    </NAlertDialogTrigger>
    <NAlertDialogContent>
      <NAlertDialogHeader>
        <NAlertDialogTitle>Are you absolutely sure?</NAlertDialogTitle>
        <NAlertDialogDescription>
          This action cannot be undone. This will permanently delete your
          account and remove your data from our servers.
        </NAlertDialogDescription>
      </NAlertDialogHeader>
      <NAlertDialogFooter>
        <NAlertDialogCancel>
          Cancel
        </NAlertDialogCancel>
        <NAlertDialogAction>Continue</NAlertDialogAction>
      </NAlertDialogFooter>
    </NAlertDialogContent>
  </NAlertDialog>
</template>
Custom Width
 Preview
 Code
<template>
  <NAlertDialog
    title="Custom Size Alert Dialog"
    description="This alert dialog has a custom size."
    :una="{
      alertDialogContent: 'max-w-7xl',
    }"
  >
    <template #trigger>
      <NButton btn="solid-gray">
        Open Dialog
      </NButton>
    </template>
  </NAlertDialog>
</template>
Custom Animation
 Preview
 Code
<template>
  <NAlertDialog
    title="Custom Animation Alert Dialog"
    description="This alert dialog has a custom animation."
    :una="{
      alertDialogOverlay: 'data-[state=open]:animate-fade-in data-[state=closed]:animate-fade-out animate-duration-250',
      alertDialogContent: 'data-[state=open]:animate-fade-in data-[state=closed]:animate-fade-out animate-duration-250',
    }"
  >
    <template #trigger>
      <NButton btn="solid-gray">
        Open Dialog
      </NButton>
    </template>
  </NAlertDialog>
</template>
Presets
 shortcuts/alert-dialog.ts 
type AlertDialogPrefix = 'alert-dialog'
export const staticAlertDialog: Record<`${AlertDialogPrefix}-${string}` | AlertDialogPrefix, string> = {
  // base
  'alert-dialog': '',
  // sub-components
  'alert-dialog-cancel': 'mt-2 sm:mt-0',
  'alert-dialog-overlay': 'fixed inset-0 z-50 bg-black/80',
  'alert-dialog-content': 'bg-background fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
  'alert-dialog-title': 'text-lg font-semibold',
  'alert-dialog-description': 'text-muted text-sm',
  'alert-dialog-header': 'flex flex-col gap-2 text-center sm:text-left',
  'alert-dialog-footer': 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
}
export const dynamicAlertDialog: [RegExp, (params: RegExpExecArray) => string][] = [
  // dynamic preset
]
export const alertDialog = [
  ...dynamicAlertDialog,
  staticAlertDialog,
]
Props
 types/alert-dialog.ts 
import type {
  AlertDialogActionProps,
  AlertDialogCancelProps,
  AlertDialogContentProps,
  AlertDialogDescriptionProps,
  AlertDialogOverlayProps,
  AlertDialogProps,
  AlertDialogTitleProps,
  AlertDialogTriggerProps,
} from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'
export interface NAlertDialogProps extends AlertDialogProps, Pick<NAlertDialogContentProps, 'preventClose' | 'overlay' | '_alertDialogCancel' | '_alertDialogAction' | '_alertDialogOverlay'> {
  /**
   * The title of the dialog.
   */
  title?: string
  /**
   * The description of the dialog.
   */
  description?: string
  // sub-components
  _alertDialogTitle?: NAlertDialogTitleProps
  _alertDialogDescription?: NAlertDialogDescriptionProps
  _alertDialogContent?: NAlertDialogContentProps
  _alertDialogTrigger?: NAlertDialogTriggerProps
  _alertDialogHeader?: NAlertDialogHeaderProps
  _alertDialogFooter?: NAlertDialogFooterProps
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/alert-dialog.ts
   */
  una?: NAlertDialogUnaProps
}
interface BaseExtensions {
  class?: HTMLAttributes['class']
}
export interface NAlertDialogTitleProps extends AlertDialogTitleProps, BaseExtensions {
  una?: Pick<NAlertDialogUnaProps, 'alertDialogTitle'>
}
export interface NAlertDialogDescriptionProps extends AlertDialogDescriptionProps, BaseExtensions {
  una?: Pick<NAlertDialogUnaProps, 'alertDialogDescription'>
}
export interface NAlertDialogTriggerProps extends AlertDialogTriggerProps {
}
export interface NAlertDialogContentProps extends AlertDialogContentProps, BaseExtensions {
  /**
   * Prevent close.
   *
   * @default true
   */
  preventClose?: boolean
  /**
   * Show overlay.
   *
   * @default true
   */
  overlay?: boolean
  /**
   * The cancel button props.
   */
  _alertDialogCancel?: NAlertDialogCancelProps
  /**
   * The action button props.
   */
  _alertDialogAction?: NAlertDialogActionProps
  /**
   * The overlay props.
   */
  _alertDialogOverlay?: NAlertDialogOverlayProps
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/alert-dialog.ts
   */
  una?: Pick<NAlertDialogUnaProps, 'alertDialogContent' | 'alertDialogOverlay'>
}
export interface NAlertDialogOverlayProps extends BaseExtensions, AlertDialogOverlayProps {
  una?: Pick<NAlertDialogUnaProps, 'alertDialogOverlay'>
}
export interface NAlertDialogCancelProps extends AlertDialogCancelProps, NButtonProps {
  /**
   * The cancel callback, triggered when the cancel button is clicked.
   */
  onClick?: (e: Event) => void
}
export interface NAlertDialogActionProps extends AlertDialogActionProps, NButtonProps {
  /**
   * The action callback, triggered when the action button is clicked.
   */
  onClick?: (e: Event) => void
}
export interface NAlertDialogHeaderProps extends BaseExtensions {
  una?: Pick<NAlertDialogUnaProps, 'alertDialogHeader'>
}
export interface NAlertDialogFooterProps extends BaseExtensions {
  una?: Pick<NAlertDialogUnaProps, 'alertDialogFooter'>
}
export interface NAlertDialogUnaProps {
  alertDialogTitle?: HTMLAttributes['class']
  alertDialogDescription?: HTMLAttributes['class']
  alertDialogOverlay?: HTMLAttributes['class']
  alertDialogContent?: HTMLAttributes['class']
  alertDialogHeader?: HTMLAttributes['class']
  alertDialogFooter?: HTMLAttributes['class']
}
Components
 AlertDialog.vue
 AlertDialogAction.vue
 AlertDialogCancel.vue
 AlertDialogContent.vue
 AlertDialogDescription.vue
 AlertDialogFooter.vue
 AlertDialogHeader.vue
 AlertDialogOverlay.vue
 AlertDialogTitle.vue
 AlertDialogTrigger.vue
<script setup lang="ts">
import type { AlertDialogEmits } from 'reka-ui'
import type { NAlertDialogProps } from '../../types'
import { reactivePick } from '@vueuse/core'
import { AlertDialogRoot, useForwardPropsEmits, VisuallyHidden } from 'reka-ui'
import { computed } from 'vue'
import { randomId } from '../../utils'
import AlertDialogAction from './AlertDialogAction.vue'
import AlertDialogCancel from './AlertDialogCancel.vue'
import AlertDialogContent from './AlertDialogContent.vue'
import AlertDialogDescription from './AlertDialogDescription.vue'
import AlertDialogFooter from './AlertDialogFooter.vue'
import AlertDialogHeader from './AlertDialogHeader.vue'
import AlertDialogTitle from './AlertDialogTitle.vue'
import AlertDialogTrigger from './AlertDialogTrigger.vue'
defineOptions({
  inheritAttrs: false,
})
const props = withDefaults(defineProps<NAlertDialogProps>(), {
  overlay: true,
})
const emits = defineEmits<AlertDialogEmits & {
  cancel: [Event]
  action: [Event]
}>()
const DEFAULT_TITLE = randomId('alert-dialog-title')
const DEFAULT_DESCRIPTION = randomId('alert-dialog-description')
const title = computed(() => props.title ?? DEFAULT_TITLE)
const description = computed(() => props.description ?? DEFAULT_DESCRIPTION)
const rootProps = reactivePick(props, [
  'open',
  'defaultOpen',
])
const forwarded = useForwardPropsEmits(rootProps, emits)
</script>
<template>
  <AlertDialogRoot
    v-slot="{ open }"
    data-slot="alert-dialog"
    v-bind="forwarded"
  >
    <slot>
      <AlertDialogTrigger
        v-bind="_alertDialogTrigger"
        as-child
      >
        <slot name="trigger" :open />
      </AlertDialogTrigger>
      <AlertDialogContent
        v-bind="_alertDialogContent"
        :_alert-dialog-overlay
        :prevent-close
        :una
      >
        <VisuallyHidden v-if="(title === DEFAULT_TITLE || !!$slots.title) || (description === DEFAULT_DESCRIPTION || !!$slots.description)">
          <AlertDialogTitle v-if="title === DEFAULT_TITLE || !!$slots.title">
            {{ title }}
          </AlertDialogTitle>
          <AlertDialogDescription v-if="description === DEFAULT_DESCRIPTION || !!$slots.description">
            {{ description }}
          </AlertDialogDescription>
        </VisuallyHidden>
        <slot name="content">
          <!-- Header -->
          <AlertDialogHeader
            v-if="!!$slots.header || (title !== DEFAULT_TITLE || !!$slots.title) || (description !== DEFAULT_DESCRIPTION || !!$slots.description)"
            v-bind="_alertDialogHeader"
            :una
          >
            <slot name="header">
              <AlertDialogTitle
                v-if="title !== DEFAULT_TITLE || !!$slots.title"
                v-bind="_alertDialogTitle"
                :una
              >
                <slot name="title">
                  {{ title }}
                </slot>
              </AlertDialogTitle>
              <AlertDialogDescription
                v-if="description !== DEFAULT_DESCRIPTION || !!$slots.description"
                v-bind="_alertDialogDescription"
                :una
              >
                <slot name="description">
                  {{ description }}
                </slot>
              </AlertDialogDescription>
            </slot>
          </AlertDialogHeader>
          <!-- Footer -->
          <AlertDialogFooter
            v-bind="_alertDialogFooter"
            :una
          >
            <slot name="footer">
              <slot name="cancel-wrapper">
                <AlertDialogCancel
                  v-bind="_alertDialogCancel"
                  @click="emits('cancel', $event)"
                >
                  <slot name="cancel" />
                </AlertDialogCancel>
              </slot>
              <slot name="action-wrapper">
                <AlertDialogAction
                  v-bind="_alertDialogAction"
                  @click="emits('action', $event)"
                >
                  <slot name="action" />
                </AlertDialogAction>
              </slot>
            </slot>
          </AlertDialogFooter>
        </slot>
      </AlertDialogContent>
    </slot>
  </AlertDialogRoot>
</template>
<script setup lang="ts">
import type { NAlertDialogActionProps } from '../../types'
import { AlertDialogAction } from 'reka-ui'
import Button from '../elements/Button.vue'
const props = withDefaults(defineProps<NAlertDialogActionProps>(), {
  btn: 'solid-primary',
  label: 'Continue',
})
</script>
<template>
  <AlertDialogAction
    as-child
  >
    <Button
      v-bind="props"
    >
      <template v-for="(_, name) in $slots" #[name]="slotData">
        <slot :name="name" v-bind="slotData" />
      </template>
    </Button>
  </AlertDialogAction>
</template>
<script setup lang="ts">
import type { NAlertDialogCancelProps } from '../../types'
import { AlertDialogCancel } from 'reka-ui'
import { cn } from '../../utils'
import Button from '../elements/Button.vue'
const props = withDefaults(defineProps<NAlertDialogCancelProps>(), {
  btn: 'solid-gray',
  label: 'Cancel',
})
</script>
<template>
  <AlertDialogCancel
    as-child
  >
    <Button
      v-bind="props"
      :class="cn(
        'alert-dialog-cancel',
        props.class,
      )"
    >
      <template v-for="(_, name) in $slots" #[name]="slotData">
        <slot :name="name" v-bind="slotData" />
      </template>
    </Button>
  </AlertDialogCancel>
</template>
<script setup lang="ts">
import type { AlertDialogContentEmits } from 'reka-ui'
import type { NAlertDialogContentProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import {
  AlertDialogContent,
  AlertDialogPortal,
  useForwardPropsEmits,
} from 'reka-ui'
import { computed } from 'vue'
import { cn } from '../../utils'
import AlertDialogOverlay from './AlertDialogOverlay.vue'
defineOptions({
  inheritAttrs: false,
})
const props = withDefaults(defineProps<NAlertDialogContentProps>(), {
  overlay: true,
})
const emits = defineEmits<AlertDialogContentEmits>()
const delegatedProps = reactiveOmit(props, ['class', 'una', '_alertDialogOverlay'])
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const contentEvents = computed(() => {
  if (props.preventClose) {
    return {
      pointerDownOutside: (e: Event) => e.preventDefault(),
      interactOutside: (e: Event) => e.preventDefault(),
      escapeKeyDown: (e: Event) => e.preventDefault(),
      closeAutoFocus: (e: Event) => e.preventDefault(),
    }
  }
  return {
    closeAutoFocus: (e: Event) => e.preventDefault(),
  }
})
</script>
<template>
  <AlertDialogPortal>
    <AlertDialogOverlay
      v-if="overlay"
      v-bind="_alertDialogOverlay"
      :una
    />
    <AlertDialogContent
      data-slot="alert-dialog-content"
      v-bind="{ ...forwarded, ...$attrs }"
      :class="cn(
        'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-48% data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-48%',
        'alert-dialog-content',
        props.una?.alertDialogContent,
        props.class,
      )"
      v-on="contentEvents"
    >
      <slot />
    </AlertDialogContent>
  </AlertDialogPortal>
</template>
<script setup lang="ts">
import type { NAlertDialogDescriptionProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { AlertDialogDescription } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NAlertDialogDescriptionProps>()
const delegatedProps = reactiveOmit(props, 'class', 'una')
</script>
<template>
  <AlertDialogDescription
    data-slot="alert-dialog-description"
    v-bind="delegatedProps"
    :class="cn(
      'alert-dialog-description',
      props.una?.alertDialogDescription,
      props.class,
    )"
  >
    <slot />
  </AlertDialogDescription>
</template>
<script setup lang="ts">
import type { NAlertDialogFooterProps } from '../../types'
import { cn } from '../../utils'
const props = defineProps<NAlertDialogFooterProps>()
</script>
<template>
  <div
    data-slot="alert-dialog-footer"
    :class="
      cn(
        'alert-dialog-footer',
        props.una?.alertDialogFooter,
        props.class,
      )
    "
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { NAlertDialogHeaderProps } from '../../types'
import { cn } from '../../utils'
const props = defineProps<NAlertDialogHeaderProps>()
</script>
<template>
  <div
    data-slot="alert-dialog-header"
    :class="cn(
      'alert-dialog-header',
      props.una?.alertDialogHeader,
      props.class,
    )"
  >
    <slot />
  </div>
</template>
<script lang="ts" setup>
import type { NAlertDialogOverlayProps } from '../../types'
import { DialogOverlay } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NAlertDialogOverlayProps>()
</script>
<template>
  <DialogOverlay
    data-slot="alert-dialog-overlay"
    :class="cn(
      'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 ',
      'alert-dialog-overlay',
      props.una?.alertDialogOverlay,
      props.class,
    )"
  >
    <slot />
  </DialogOverlay>
</template>
<script setup lang="ts">
import type { NAlertDialogTitleProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { AlertDialogTitle } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NAlertDialogTitleProps>()
const delegatedProps = reactiveOmit(props, 'class', 'una')
</script>
<template>
  <AlertDialogTitle
    data-slot="alert-dialog-title"
    v-bind="delegatedProps"
    :class="cn(
      'alert-dialog-title',
      props.una?.alertDialogTitle,
      props.class,
    )"
  >
    <slot />
  </AlertDialogTitle>
</template>
<script setup lang="ts">
import type { NAlertDialogTriggerProps } from '../../types'
import { AlertDialogTrigger } from 'reka-ui'
const props = defineProps<NAlertDialogTriggerProps>()
</script>
<template>
  <AlertDialogTrigger
    data-slot="alert-dialog-trigger"
    v-bind="props"
  >
    <slot />
  </AlertDialogTrigger>
</template>