Alert Dialog

A modal dialog that interrupts the user with important content and expects a response.

Examples

Basic

PropDefaultTypeDescription
title-stringThe title of the dialog.
description-stringThe description of the dialog.
defaultOpenfalsebooleanThe open state of the dialog when it is initially rendered. Use when you do not need to control its open state.
open-booleanThe controlled open state of the dialog. Can be bind as v-model:open.
overlaytruebooleanShow the overlay.
@action-eventEvent emitted when the action button is clicked, typically used for confirming destructive actions.
@cancel-eventEvent emitted when the cancel button is clicked or dialog is dismissed, used for aborting the proposed action.
Preview
Code

Buttons

PropDefaultTypeDescription
_alertDialogCancel{ btn: 'solid-gray', label: 'Cancel'AlertDialogCancelPropsThe cancel button props.
_alertDialogAction{ btn: 'soft-primary', label: 'Continue' }AlertDialogActionPropsThe action button props.
Preview
Code

Prevent Closing

PropDefaultTypeDescription
preventClose-booleanIf true, the alert dialog will not close on overlay click or escape key press.
Preview
Code

Slots

NamePropsDescription
default-Allows advanced customization using sub-components, replacing the default structure.
content-Replaces the entire default content container within the dialog popup.
triggeropenThe 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

Custom Width

Preview
Code

Custom Animation

Preview
Code

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': 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-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 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>