---
name: alert-dialog
title: Alert Dialog
url: https://ui.frontic.com/components/ui/alert-dialog.md
tier: ui
install: npx @frontic/ui add alert-dialog
files:
  - components/ui/alert-dialog/AlertDialog.vue
  - components/ui/alert-dialog/AlertDialogAction.vue
  - components/ui/alert-dialog/AlertDialogCancel.vue
  - components/ui/alert-dialog/AlertDialogContent.vue
  - components/ui/alert-dialog/AlertDialogDescription.vue
  - components/ui/alert-dialog/AlertDialogFooter.vue
  - components/ui/alert-dialog/AlertDialogHeader.vue
  - components/ui/alert-dialog/AlertDialogMedia.vue
  - components/ui/alert-dialog/AlertDialogTitle.vue
  - components/ui/alert-dialog/AlertDialogTrigger.vue
  - components/ui/alert-dialog/index.ts
registryDependencies:
  - button
dependencies:
  - "@vueuse/core"
  - class-variance-authority
  - reka-ui
---

# Alert Dialog

Asks the shopper to confirm or cancel an action before it happens, such as removing an address, and keeps focus inside until they choose.

## Install

```sh
npx @frontic/ui add alert-dialog
```

Built from: [Button](https://ui.frontic.com/components/ui/button.md)

npm packages: `@vueuse/core`, `class-variance-authority`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { Trash2Icon } from '@lucide/vue'
import { Button } from '@/components/ui/button'
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@/components/ui/alert-dialog'
</script>

<template>
  <AlertDialog default-open>
    <AlertDialogTrigger as-child><Button variant="outline" color="destructive">Delete address</Button></AlertDialogTrigger>
    <AlertDialogContent>
      <AlertDialogHeader>
        <AlertDialogMedia>
          <Trash2Icon data-icon />
        </AlertDialogMedia>
        <AlertDialogTitle>Delete this address?</AlertDialogTitle>
        <AlertDialogDescription>It leaves your address book. Orders already placed keep it.</AlertDialogDescription>
      </AlertDialogHeader>
      <AlertDialogFooter>
        <AlertDialogCancel>Cancel</AlertDialogCancel>
        <AlertDialogAction color="destructive">Delete</AlertDialogAction>
      </AlertDialogFooter>
    </AlertDialogContent>
  </AlertDialog>
</template>
```

## Anatomy

```vue
<AlertDialog>
  <AlertDialogTrigger />
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogMedia />
      <AlertDialogTitle />
      <AlertDialogDescription />
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel />
      <AlertDialogAction />
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>
```

## API reference

### AlertDialog

Groups all parts of the alert dialog.

`data-slot="alert-dialog"` · `AlertDialog.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `unmountOnHide` | `boolean` | `true` | When set to `false`, the dialog content will not be unmounted when closed, but instead hidden with CSS.<br><br>Useful for SEO or when you want to improve performance by not remounting the component on every open. |
| `open` | `boolean` | `false` | The controlled open state of the dialog. Can be binded as `v-model:open`. |
| `defaultOpen` | `boolean` | `false` | The open state of the dialog when it is initially rendered. Use when you do not need to control its open state. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:open` | `value: boolean` | Event handler called when the open state of the dialog changes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ open: boolean; close: () => void; }` | Holds the alert dialog's trigger and content; gets whether it is open and a function that closes it. |

### AlertDialogAction

Confirms the action the dialog asks about and closes the dialog. Renders a `<button>`.

`data-slot="alert-dialog-action"` · `AlertDialogAction.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | `'solid'` | The button style: solid, outline, soft, ghost or link. |
| `color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | `'primary'` | The button colour, e.g. primary, neutral or destructive for an irreversible action. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's height, padding and text size. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | Carries the `color` prop. |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |
| `data-variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | Carries the `variant` prop. |

### AlertDialogCancel

Dismisses the dialog without taking the action. Renders a `<button>`.

`data-slot="alert-dialog-cancel"` · `AlertDialogCancel.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | `'outline'` | The button style: solid, outline, soft, ghost or link. |
| `color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | `'neutral'` | The button colour, e.g. primary, neutral or destructive for an irreversible action. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's height, padding and text size. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | Carries the `color` prop. |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |
| `data-variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | Carries the `variant` prop. |

### AlertDialogContent

Holds what the alert dialog shows while open.

`data-slot="alert-dialog-content"` · `AlertDialogContent.vue`

Styling: `alert-dialog-content`, which extends [`overlay-modal`](https://ui.frontic.com/DESIGN.md#overlay-modal) and [`surface-inset`](https://ui.frontic.com/DESIGN.md#surface-inset); `alert-dialog-overlay`, which extends [`overlay-backdrop`](https://ui.frontic.com/DESIGN.md#overlay-backdrop). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `forceMount` | `boolean` | `false` | Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `disableOutsidePointerEvents` | `boolean` |  | When `true`, hover/focus/click interactions will be disabled on elements outside the `DismissableLayer`. Users will need to click twice on outside elements to interact with them: once to close the `DismissableLayer`, and again to trigger the element. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `size` | `'md' \| 'sm'` | `'md'` | The dialog width: `sm` stays narrow, `md` widens on larger screens. |

| Event | Payload | Description |
| --- | --- | --- |
| `escapeKeyDown` | `event: KeyboardEvent` | Event handler called when the escape key is down. Can be prevented. |
| `pointerDownOutside` | `event: PointerDownOutsideEvent` | Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented. |
| `focusOutside` | `event: FocusOutsideEvent` | Event handler called when the focus moves outside of the `DismissableLayer`. Can be prevented. |
| `interactOutside` | `event: PointerDownOutsideEvent \| FocusOutsideEvent` | Event handler called when an interaction happens outside the `DismissableLayer`. Specifically, when a `pointerdown` event happens outside or focus moves outside of it. Can be prevented. |
| `openAutoFocus` | `event: Event` | Event handler called when auto-focusing on open. Can be prevented. |
| `closeAutoFocus` | `event: Event` | Event handler called when auto-focusing on close. Can be prevented. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-dismissable-layer` |  | Marks a layer that closes on Escape or a click outside it. |
| `data-size` | `'md' \| 'sm'` | Carries the `size` prop. |
| `data-size-sm` |  | Present when `size` is `sm`. |
| `data-state` | `'open' \| 'closed'` | The state the part is in, one value per state. |

### AlertDialogDescription

Explains what the action in the dialog will do. Renders a `<p>`.

`data-slot="alert-dialog-description"` · `AlertDialogDescription.vue`

Styling: `alert-dialog-description`, which extends [`part-description`](https://ui.frontic.com/DESIGN.md#part-description) and [`link-foreground`](https://ui.frontic.com/DESIGN.md#link-foreground). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'p'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### AlertDialogFooter

Holds the cancel and confirm buttons at the bottom of the dialog. Renders a `<div>`.

`data-slot="alert-dialog-footer"` · `AlertDialogFooter.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### AlertDialogHeader

Groups the title and description at the top of the dialog. Renders a `<div>`.

`data-slot="alert-dialog-header"` · `AlertDialogHeader.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### AlertDialogMedia

Holds an icon or image that illustrates the dialog. Renders a `<div>`.

`data-slot="alert-dialog-media"` · `AlertDialogMedia.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### AlertDialogTitle

Names the action the dialog asks the user to confirm. Renders a `<h2>`.

`data-slot="alert-dialog-title"` · `AlertDialogTitle.vue`

Styling: `alert-dialog-title`, which extends [`part-title-lg`](https://ui.frontic.com/DESIGN.md#part-title-lg). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'h2'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### AlertDialogTrigger

Opens the alert dialog. Renders a `<button>`.

`data-slot="alert-dialog-trigger"` · `AlertDialogTrigger.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-state` | `'open' \| 'closed'` | The state the part is in, one value per state. |

## Exports

```ts
import { type AlertDialogButtonProps } from '@/components/ui/alert-dialog'
```

- `AlertDialogButtonProps`: below

```ts
interface AlertDialogButtonProps {
  /** The button style: solid, outline, soft, ghost or link. */
  variant?: InstanceType<typeof Button>['$props']['variant']
  /** The button colour, e.g. primary, neutral or destructive for an irreversible action. */
  color?: InstanceType<typeof Button>['$props']['color']
  /** The button's height, padding and text size. */
  size?: InstanceType<typeof Button>['$props']['size']
  class?: HTMLAttributes['class']
}
```

## Styling

| Part | Recipe |
| --- | --- |
| `content` | [`alert-dialog-content`](https://ui.frontic.com/DESIGN.md#alert-dialog-content) |
| `header` | [`alert-dialog-header`](https://ui.frontic.com/DESIGN.md#alert-dialog-header) |
| `footer` | [`alert-dialog-footer`](https://ui.frontic.com/DESIGN.md#alert-dialog-footer) |
| `media` | [`alert-dialog-media`](https://ui.frontic.com/DESIGN.md#alert-dialog-media) |
| `overlay` | [`alert-dialog-overlay`](https://ui.frontic.com/DESIGN.md#alert-dialog-overlay) |
| `title` | [`alert-dialog-title`](https://ui.frontic.com/DESIGN.md#alert-dialog-title) |
| `description` | [`alert-dialog-description`](https://ui.frontic.com/DESIGN.md#alert-dialog-description) |

## Source

`components/ui/alert-dialog/AlertDialog.vue`

```vue
<script lang="ts">
/** Groups all parts of the alert dialog. */
export default {}
</script>

<script setup lang="ts">
import type { AlertDialogEmits, AlertDialogProps } from 'reka-ui'
import { AlertDialogRoot, useForwardPropsEmits } from 'reka-ui'

const props = defineProps<AlertDialogProps>()
const emits = defineEmits<AlertDialogEmits>()

defineSlots<{
  /** Holds the alert dialog's trigger and content; gets whether it is open and a function that closes it. */
  default?: (props: { open: boolean; close: () => void }) => unknown
}>()

const forwarded = useForwardPropsEmits(props, emits)
</script>

<template>
  <AlertDialogRoot v-slot="slotProps" data-slot="alert-dialog" v-bind="forwarded">
    <slot v-bind="slotProps" />
  </AlertDialogRoot>
</template>
```

`components/ui/alert-dialog/AlertDialogAction.vue`

```vue
<script lang="ts">
/** Confirms the action the dialog asks about and closes the dialog. */
export default {}
</script>

<script setup lang="ts">
import type { AlertDialogActionProps } from 'reka-ui'
import type { AlertDialogButtonProps } from '.'
import { reactiveOmit } from '@vueuse/core'
import { AlertDialogAction, useForwardProps } from 'reka-ui'
import { Button } from '@library/components/ui/button'

const props = withDefaults(defineProps<AlertDialogActionProps & AlertDialogButtonProps>(), {
  variant: 'solid',
  color: 'primary',
  size: 'md',
})

const delegatedProps = reactiveOmit(props, 'class', 'variant', 'color', 'size')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <AlertDialogAction data-slot="alert-dialog-action" v-bind="forwarded" as-child>
    <Button :variant="props.variant" :color="props.color" :size="props.size" :class="props.class">
      <slot />
    </Button>
  </AlertDialogAction>
</template>
```

`components/ui/alert-dialog/AlertDialogCancel.vue`

```vue
<script lang="ts">
/** Dismisses the dialog without taking the action. */
export default {}
</script>

<script setup lang="ts">
import type { AlertDialogCancelProps } from 'reka-ui'
import type { AlertDialogButtonProps } from '.'
import { reactiveOmit } from '@vueuse/core'
import { AlertDialogCancel, useForwardProps } from 'reka-ui'
import { Button } from '@library/components/ui/button'

const props = withDefaults(defineProps<AlertDialogCancelProps & AlertDialogButtonProps>(), {
  variant: 'outline',
  color: 'neutral',
  size: 'md',
})

const delegatedProps = reactiveOmit(props, 'class', 'variant', 'color', 'size')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <AlertDialogCancel data-slot="alert-dialog-cancel" v-bind="forwarded" as-child>
    <Button :variant="props.variant" :color="props.color" :size="props.size" :class="props.class">
      <slot />
    </Button>
  </AlertDialogCancel>
</template>
```

`components/ui/alert-dialog/AlertDialogContent.vue`

```vue
<script lang="ts">
/** Holds what the alert dialog shows while open. */
export default {}
</script>

<script setup lang="ts">
import type { AlertDialogContentEmits, AlertDialogContentProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { cva } from 'class-variance-authority'
import { AlertDialogContent, AlertDialogOverlay, AlertDialogPortal, injectDialogRootContext, Presence, useForwardPropsEmits } from 'reka-ui'

defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(
  defineProps<
    AlertDialogContentProps & {
      class?: HTMLAttributes['class']
      /** The dialog width: `sm` stays narrow, `md` widens on larger screens. */
      size?: 'md' | 'sm'
    }
  >(),
  {
    size: 'md',
  },
)
const emits = defineEmits<AlertDialogContentEmits>()

const content = cva('fui-alert-dialog-content group/alert-dialog-content @container pointer-events-auto fixed inset-x-4 top-1/2 z-50 -translate-y-1/2', {
  variants: { size: { md: 'fui-alert-dialog-content-md', sm: 'fui-alert-dialog-content-sm' } },
  defaultVariants: { size: 'md' },
})

const delegatedProps = reactiveOmit(props, 'class', 'size')

const forwarded = useForwardPropsEmits(delegatedProps, emits)
const root = injectDialogRootContext()
</script>

<template>
  <AlertDialogPortal>
    <AlertDialogOverlay
      data-slot="alert-dialog-overlay"
      class="fui-alert-dialog-overlay data-open:animate-in data-closed:animate-out data-open:fade-in-0 data-closed:fade-out-0 duration-100 fixed inset-0 z-50 supports-backdrop-filter:backdrop-blur-sm"
    />
    <Presence v-slot="{ present }" :present="root.open.value" :force-mount="props.forceMount || !root.unmountOnHide.value">
      <div
        v-show="props.forceMount || root.unmountOnHide.value || present"
        data-slot="alert-dialog-frame"
        :data-state="root.open.value ? 'open' : 'closed'"
        class="pointer-events-none fixed inset-0 z-50 @container data-open:animate-in data-closed:animate-out data-open:fade-in-0 data-closed:fade-out-0 data-open:zoom-in-95 data-closed:zoom-out-95 duration-100"
      >
        <AlertDialogContent
          data-slot="alert-dialog-content"
          :data-size="size"
          :data-size-sm="size === 'sm' ? '' : undefined"
          v-bind="{ ...$attrs, ...forwarded }"
          force-mount
          :class="[content({ size }), props.class]"
        >
          <slot />
        </AlertDialogContent>
      </div>
    </Presence>
  </AlertDialogPortal>
</template>
```

`components/ui/alert-dialog/AlertDialogDescription.vue`

```vue
<script lang="ts">
/** Explains what the action in the dialog will do. */
export default {}
</script>

<script setup lang="ts">
import type { AlertDialogDescriptionProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AlertDialogDescription, useForwardProps } from 'reka-ui'

const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes['class'] }>()

const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <AlertDialogDescription data-slot="alert-dialog-description" v-bind="forwarded" :class="['fui-alert-dialog-description text-balance @md:text-pretty', props.class]">
    <slot />
  </AlertDialogDescription>
</template>
```

`components/ui/alert-dialog/AlertDialogFooter.vue`

```vue
<script lang="ts">
/** Holds the cancel and confirm buttons at the bottom of the dialog. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive
    :as="props.as"
    :as-child="props.asChild"
    data-slot="alert-dialog-footer"
    :class="['fui-alert-dialog-footer group-data-size-sm/alert-dialog-content:grid group-data-size-sm/alert-dialog-content:grid-cols-2', props.class]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/alert-dialog/AlertDialogHeader.vue`

```vue
<script lang="ts">
/** Groups the title and description at the top of the dialog. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="alert-dialog-header" :class="['fui-alert-dialog-header has-[[data-slot=alert-dialog-media]]:gap-x-6', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/alert-dialog/AlertDialogMedia.vue`

```vue
<script lang="ts">
/** Holds an icon or image that illustrates the dialog. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="alert-dialog-media" :class="['fui-alert-dialog-media group-data-size-sm/alert-dialog-content:row-auto', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/alert-dialog/AlertDialogTitle.vue`

```vue
<script lang="ts">
/** Names the action the dialog asks the user to confirm. */
export default {}
</script>

<script setup lang="ts">
import type { AlertDialogTitleProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AlertDialogTitle, useForwardProps } from 'reka-ui'

const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes['class'] }>()

const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <AlertDialogTitle
    data-slot="alert-dialog-title"
    v-bind="forwarded"
    :class="['fui-alert-dialog-title @sm:group-has-[[data-slot=alert-dialog-media]]/alert-dialog-content:col-start-2 group-data-size-sm/alert-dialog-content:col-auto', props.class]"
  >
    <slot />
  </AlertDialogTitle>
</template>
```

`components/ui/alert-dialog/AlertDialogTrigger.vue`

```vue
<script lang="ts">
/** Opens the alert dialog. */
export default {}
</script>

<script setup lang="ts">
import type { AlertDialogTriggerProps } from 'reka-ui'
import { AlertDialogTrigger, useForwardProps } from 'reka-ui'

const props = defineProps<AlertDialogTriggerProps>()
const forwarded = useForwardProps(props)
</script>

<template>
  <AlertDialogTrigger data-slot="alert-dialog-trigger" v-bind="forwarded">
    <slot />
  </AlertDialogTrigger>
</template>
```

`components/ui/alert-dialog/index.ts`

```ts
import type { HTMLAttributes } from 'vue'
import type { Button } from '@library/components/ui/button'

export { default as AlertDialog } from './AlertDialog.vue'
export { default as AlertDialogAction } from './AlertDialogAction.vue'
export { default as AlertDialogCancel } from './AlertDialogCancel.vue'
export { default as AlertDialogContent } from './AlertDialogContent.vue'
export { default as AlertDialogDescription } from './AlertDialogDescription.vue'
export { default as AlertDialogFooter } from './AlertDialogFooter.vue'
export { default as AlertDialogHeader } from './AlertDialogHeader.vue'
export { default as AlertDialogMedia } from './AlertDialogMedia.vue'
export { default as AlertDialogTitle } from './AlertDialogTitle.vue'
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'

export interface AlertDialogButtonProps {
  /** The button style: solid, outline, soft, ghost or link. */
  variant?: InstanceType<typeof Button>['$props']['variant']
  /** The button colour, e.g. primary, neutral or destructive for an irreversible action. */
  color?: InstanceType<typeof Button>['$props']['color']
  /** The button's height, padding and text size. */
  size?: InstanceType<typeof Button>['$props']['size']
  class?: HTMLAttributes['class']
}
```

## API of the items the example uses

### Button

[Full docs](https://ui.frontic.com/components/ui/button.md) · `npx @frontic/ui add button`

#### Button

Lets the user trigger an action, such as adding a product to the cart. Renders a `<button>`.

`data-slot="button"` · `Button.vue`

Styling: `button`, which extends [`action`](https://ui.frontic.com/DESIGN.md#action). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | `'solid'` | The visual style: a filled, outlined, tinted, background-free or text-link button. |
| `color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | `'primary'` | The colour role, e.g. primary for the brand colour, buy for add-to-cart, checkout for the checkout step, destructive for removing. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's height and padding; the `icon-*` sizes make a square, icon-only button. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | Carries the `color` prop. |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |
| `data-variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | Carries the `variant` prop. |
