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 on overlay click or 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': '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>
<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>