Skip to main content
Nexus Design System
GitHub

AlertDialog

AlertDialog makes the user confirm or cancel before an action with serious results, such as deleting data. For lower-stakes modal tasks, use Dialog. For edge panels, use Sheet, or Drawer for drag-to-dismiss.

'use client';

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@/components/nxds/components/alert-dialog/alert-dialog';
import { Button } from '@/components/nxds/components/button/button';

export default function AlertDialogDefault() {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button variant="outline">Show dialog</Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
          <AlertDialogDescription>
            This action cannot be undone. This will permanently update your
            account settings.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Continue</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  );
}

Installation

npx nxds-cli@latest add alert-dialog

Run npx nxds-cli@latest init first if nxds isn’t set up in your app yet. See Install.

Usage

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@/components/nxds/components/alert-dialog/alert-dialog';

Composition

AlertDialog
├── AlertDialogTrigger
└── AlertDialogContent
    ├── AlertDialogHeader
    │   ├── AlertDialogTitle
    │   └── AlertDialogDescription
    └── AlertDialogFooter
        ├── AlertDialogCancel
        └── AlertDialogAction

Examples

Destructive

Confirm a permanent delete with a destructive action button.

'use client';

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@/components/nxds/components/alert-dialog/alert-dialog';
import { Button } from '@/components/nxds/components/button/button';

export default function AlertDialogDestructive() {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button variant="destructive">Delete account</Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Delete your account?</AlertDialogTitle>
          <AlertDialogDescription>
            This action cannot be undone. This will permanently delete your
            account and remove your data from our servers.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction variant="destructive">Delete</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  );
}

Centered

Center the header and stack full-width actions with variant="center".

'use client';

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@/components/nxds/components/alert-dialog/alert-dialog';
import { Button } from '@/components/nxds/components/button/button';

export default function AlertDialogCentered() {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button variant="destructive">Show centered dialog</Button>
      </AlertDialogTrigger>
      <AlertDialogContent variant="center">
        <AlertDialogHeader>
          <AlertDialogTitle>Dialog Title</AlertDialogTitle>
          <AlertDialogDescription>
            Make changes to your profile here. Click save when you are done.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction variant="destructive">Action</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  );
}

Props

AlertDialog

AlertDialog adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogAction

AlertDialogAction props
PropTypeDefaultDescription
size"default" | "sm" | "lg" | "icon-sm" | "icon" | "icon-lg" | null"default"No description
variant"link" | "default" | "error" | "destructive" | "outline" | "dashed" | "secondary" | "ghost" | null"default"No description

AlertDialogBody

AlertDialogBody adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogCancel

AlertDialogCancel props
PropTypeDefaultDescription
size"default" | "sm" | "lg" | "icon-sm" | "icon" | "icon-lg" | null"default"No description
variant"link" | "default" | "error" | "destructive" | "outline" | "dashed" | "secondary" | "ghost" | null"outline"No description

AlertDialogContent

AlertDialogContent props
PropTypeDefaultDescription
variant"default" | "center""default"

Layout. default left-aligns the header and puts actions in a row. center centers the header and stacks full-width actions.

AlertDialogDescription

AlertDialogDescription adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogFooter adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogHeader

AlertDialogHeader props
PropTypeDefaultDescription
variant"default" | "center" | null"default"No description

AlertDialogOverlay

AlertDialogOverlay adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogPortal

AlertDialogPortal adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogTitle

AlertDialogTitle adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogTrigger

AlertDialogTrigger adds no props of its own; it accepts the props of the element or primitive it wraps.

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

<template>
  <AlertDialog>
    <AlertDialogTrigger as-child>
      <Button variant="outline">Show dialog</Button>
    </AlertDialogTrigger>
    <AlertDialogContent>
      <AlertDialogHeader>
        <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
        <AlertDialogDescription
          >This action cannot be undone. This will permanently update your
          account settings.</AlertDialogDescription
        >
      </AlertDialogHeader>
      <AlertDialogFooter>
        <AlertDialogCancel>Cancel</AlertDialogCancel>
        <AlertDialogAction>Continue</AlertDialogAction>
      </AlertDialogFooter>
    </AlertDialogContent>
  </AlertDialog>
</template>

This example also needs: Button.

Installation

  1. Install these components first:

    Button, ButtonGroup, Spinner.

  2. Copy and paste the following code into your project.

    components/alert-dialog/AlertDialogAction.vue
    <script setup lang="ts">
    import { reactiveOmit } from '@vueuse/core';
    import {
      AlertDialogAction,
      type AlertDialogActionProps as RekaAlertDialogActionProps,
      useForwardProps,
    } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    import { type ButtonVariants, buttonVariants } from '../button';
    
    export interface AlertDialogActionProps extends RekaAlertDialogActionProps {
      variant?: ButtonVariants['variant'];
      size?: ButtonVariants['size'];
      class?: HTMLAttributes['class'];
    }
    
    const props = withDefaults(defineProps<AlertDialogActionProps>(), {
      variant: 'default',
    });
    
    const delegated = reactiveOmit(props, 'class', 'variant', 'size');
    const forwarded = useForwardProps(delegated);
    </script>
    
    <template>
      <AlertDialogAction
        v-bind="forwarded"
        data-slot="alert-dialog-action"
        :data-variant="variant"
        :data-size="size"
        :class="cn(buttonVariants({ variant, size }), props.class)"
      >
        <slot />
      </AlertDialogAction>
    </template>
    
    components/alert-dialog/AlertDialogBody.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    import { overlayBodyClassName } from '../overlay-layout/overlay-layout';
    
    export interface AlertDialogBodyProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<AlertDialogBodyProps>();
    </script>
    
    <template>
      <div
        data-slot="alert-dialog-body"
        tabindex="0"
        :class="cn(overlayBodyClassName, props.class)"
      >
        <slot />
      </div>
    </template>
    
    components/alert-dialog/AlertDialogCancel.vue
    <script setup lang="ts">
    import { reactiveOmit } from '@vueuse/core';
    import {
      AlertDialogCancel,
      type AlertDialogCancelProps as RekaAlertDialogCancelProps,
      useForwardProps,
    } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    import { type ButtonVariants, buttonVariants } from '../button';
    
    export interface AlertDialogCancelProps extends RekaAlertDialogCancelProps {
      variant?: ButtonVariants['variant'];
      size?: ButtonVariants['size'];
      class?: HTMLAttributes['class'];
    }
    
    const props = withDefaults(defineProps<AlertDialogCancelProps>(), {
      variant: 'outline',
    });
    
    const delegated = reactiveOmit(props, 'class', 'variant', 'size');
    const forwarded = useForwardProps(delegated);
    </script>
    
    <template>
      <AlertDialogCancel
        v-bind="forwarded"
        data-slot="alert-dialog-cancel"
        :data-variant="variant"
        :data-size="size"
        :class="cn(buttonVariants({ variant, size }), props.class)"
      >
        <slot />
      </AlertDialogCancel>
    </template>
    
    components/alert-dialog/AlertDialogContent.vue
    <script setup lang="ts">
    import { reactiveOmit } from '@vueuse/core';
    import {
      AlertDialogContent,
      type AlertDialogContentEmits,
      type AlertDialogContentProps as RekaAlertDialogContentProps,
      AlertDialogPortal,
      useForwardPropsEmits,
    } from 'reka-ui';
    import { computed, type HTMLAttributes, provide, reactive } from 'vue';
    
    import { cn } from '../../lib/utils';
    import {
      overlayContentVariants,
      resolveOverlayButtonOrientation,
    } from '../overlay-layout/overlay-layout';
    
    import AlertDialogOverlay from './AlertDialogOverlay.vue';
    
    import { AlertDialogLayoutKey, type AlertDialogVariant } from '.';
    
    export interface AlertDialogContentProps extends RekaAlertDialogContentProps {
      /**
       * Layout. `default` left-aligns the header and puts actions in a row.
       * `center` centers the header and stacks full-width actions.
       */
      variant?: AlertDialogVariant;
      class?: HTMLAttributes['class'];
    }
    
    defineOptions({ inheritAttrs: false });
    
    const props = withDefaults(defineProps<AlertDialogContentProps>(), {
      variant: 'default',
    });
    const emits = defineEmits<AlertDialogContentEmits>();
    
    const delegated = reactiveOmit(props, 'class', 'variant');
    const forwarded = useForwardPropsEmits(delegated, emits);
    
    const buttonOrientation = computed(() =>
      resolveOverlayButtonOrientation(props.variant)
    );
    
    provide(
      AlertDialogLayoutKey,
      reactive({ variant: computed(() => props.variant), buttonOrientation })
    );
    </script>
    
    <template>
      <AlertDialogPortal>
        <AlertDialogOverlay />
        <AlertDialogContent
          v-bind="{ ...$attrs, ...forwarded }"
          data-slot="alert-dialog-content"
          :data-variant="variant"
          :data-orientation="buttonOrientation"
          :class="
            cn(
              overlayContentVariants(),
              variant === 'center' && 'nx:max-w-xs',
              props.class
            )
          "
        >
          <slot />
        </AlertDialogContent>
      </AlertDialogPortal>
    </template>
    
    components/alert-dialog/AlertDialogDescription.vue
    <script setup lang="ts">
    import { reactiveOmit } from '@vueuse/core';
    import {
      AlertDialogDescription,
      type AlertDialogDescriptionProps as RekaAlertDialogDescriptionProps,
      useForwardProps,
    } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface AlertDialogDescriptionProps extends RekaAlertDialogDescriptionProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<AlertDialogDescriptionProps>();
    
    const delegated = reactiveOmit(props, 'class');
    const forwarded = useForwardProps(delegated);
    </script>
    
    <template>
      <AlertDialogDescription
        v-bind="forwarded"
        data-slot="alert-dialog-description"
        :class="
          cn(
            'nx:m-0 nx:typography-body-default nx:text-muted-foreground',
            props.class
          )
        "
      >
        <slot />
      </AlertDialogDescription>
    </template>
    
    components/alert-dialog/AlertDialogFooter.vue
    <script setup lang="ts">
    import { type HTMLAttributes, inject } from 'vue';
    
    import { cn } from '../../lib/utils';
    import {
      defaultOverlayLayout,
      overlayFooterVariants,
    } from '../overlay-layout/overlay-layout';
    
    import { AlertDialogLayoutKey } from '.';
    
    export interface AlertDialogFooterProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<AlertDialogFooterProps>();
    
    const layout = inject(AlertDialogLayoutKey, defaultOverlayLayout);
    </script>
    
    <template>
      <div
        data-slot="alert-dialog-footer"
        :data-orientation="layout.buttonOrientation"
        :class="
          cn(
            overlayFooterVariants({ orientation: layout.buttonOrientation }),
            props.class
          )
        "
      >
        <slot />
      </div>
    </template>
    
    components/alert-dialog/AlertDialogHeader.vue
    <script setup lang="ts">
    import { computed, type HTMLAttributes, inject } from 'vue';
    
    import { cn } from '../../lib/utils';
    import {
      defaultOverlayLayout,
      type OverlayHeaderVariants,
      overlayHeaderVariants,
    } from '../overlay-layout/overlay-layout';
    
    import { AlertDialogLayoutKey } from '.';
    
    export interface AlertDialogHeaderProps {
      variant?: OverlayHeaderVariants['variant'];
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<AlertDialogHeaderProps>();
    
    const layout = inject(AlertDialogLayoutKey, defaultOverlayLayout);
    const resolvedVariant = computed(() => props.variant ?? layout.variant);
    </script>
    
    <template>
      <div
        data-slot="alert-dialog-header"
        :data-variant="resolvedVariant"
        :class="
          cn(overlayHeaderVariants({ variant: resolvedVariant }), props.class)
        "
      >
        <slot />
      </div>
    </template>
    
    components/alert-dialog/AlertDialogOverlay.vue
    <script setup lang="ts">
    import { reactiveOmit } from '@vueuse/core';
    import {
      AlertDialogOverlay,
      type AlertDialogOverlayProps as RekaAlertDialogOverlayProps,
      useForwardProps,
    } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    import { overlayScrimVariants } from '../overlay-layout/overlay-layout';
    
    export interface AlertDialogOverlayProps extends RekaAlertDialogOverlayProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<AlertDialogOverlayProps>();
    
    const delegated = reactiveOmit(props, 'class');
    const forwarded = useForwardProps(delegated);
    </script>
    
    <template>
      <AlertDialogOverlay
        v-bind="forwarded"
        data-slot="alert-dialog-overlay"
        :class="cn(overlayScrimVariants(), props.class)"
      />
    </template>
    
    components/alert-dialog/AlertDialogTitle.vue
    <script setup lang="ts">
    import { reactiveOmit } from '@vueuse/core';
    import {
      AlertDialogTitle,
      type AlertDialogTitleProps as RekaAlertDialogTitleProps,
      useForwardProps,
    } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface AlertDialogTitleProps extends RekaAlertDialogTitleProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<AlertDialogTitleProps>();
    
    const delegated = reactiveOmit(props, 'class');
    const forwarded = useForwardProps(delegated);
    </script>
    
    <template>
      <AlertDialogTitle
        v-bind="forwarded"
        data-slot="alert-dialog-title"
        :class="cn('nx:m-0 nx:typography-heading-small', props.class)"
      >
        <slot />
      </AlertDialogTitle>
    </template>
    
    components/alert-dialog/index.ts
    import type { InjectionKey } from 'vue';
    
    import type {
      OverlayLayoutContextValue,
      OverlayVariant,
    } from '../overlay-layout/overlay-layout';
    
    export {
      default as AlertDialogAction,
      type AlertDialogActionProps,
    } from './AlertDialogAction.vue';
    export {
      default as AlertDialogBody,
      type AlertDialogBodyProps,
    } from './AlertDialogBody.vue';
    export {
      default as AlertDialogCancel,
      type AlertDialogCancelProps,
    } from './AlertDialogCancel.vue';
    export {
      default as AlertDialogContent,
      type AlertDialogContentProps,
    } from './AlertDialogContent.vue';
    export {
      default as AlertDialogDescription,
      type AlertDialogDescriptionProps,
    } from './AlertDialogDescription.vue';
    export {
      default as AlertDialogFooter,
      type AlertDialogFooterProps,
    } from './AlertDialogFooter.vue';
    export {
      default as AlertDialogHeader,
      type AlertDialogHeaderProps,
    } from './AlertDialogHeader.vue';
    export {
      default as AlertDialogOverlay,
      type AlertDialogOverlayProps,
    } from './AlertDialogOverlay.vue';
    export {
      default as AlertDialogTitle,
      type AlertDialogTitleProps,
    } from './AlertDialogTitle.vue';
    export {
      AlertDialogRoot as AlertDialog,
      AlertDialogPortal,
      type AlertDialogPortalProps,
      type AlertDialogProps,
      AlertDialogTrigger,
      type AlertDialogTriggerProps,
    } from 'reka-ui';
    
    export type AlertDialogVariant = OverlayVariant;
    
    export const AlertDialogLayoutKey: InjectionKey<OverlayLayoutContextValue> =
      Symbol('alert-dialog-layout');
    
    components/overlay-layout/overlay-layout.ts
    import { cva, type VariantProps } from 'class-variance-authority';
    
    /**
     * Popover-family surfaces share the translucent/blurred recipe; Tooltip uses a
     * solid carve-out because its small, brief text appears over arbitrary content.
     */
    export const popoverSurfaceClassName = [
      'nx:rounded-md',
      'nx:border-default',
      'nx:border-border-default',
      'nx:bg-popover-alpha',
      'nx:backdrop-blur-md',
      'nx:text-popover-foreground',
      'nx:typography-body-default',
      'nx:shadow-lg',
    ].join(' ');
    
    export const tooltipSurfaceClassName = [
      'nx:rounded-md',
      'nx:border-default',
      'nx:border-border-default',
      'nx:bg-popover',
      'nx:text-popover-foreground',
      'nx:typography-body-default',
      'nx:shadow-lg',
    ].join(' ');
    
    export const navigationMenuInlinePopoverSurfaceClassName = [
      'nx:group-data-[viewport=false]/navigation-menu:rounded-md',
      'nx:group-data-[viewport=false]/navigation-menu:border-default',
      'nx:group-data-[viewport=false]/navigation-menu:border-border-default',
      'nx:group-data-[viewport=false]/navigation-menu:bg-popover-alpha',
      'nx:group-data-[viewport=false]/navigation-menu:backdrop-blur-md',
      'nx:group-data-[viewport=false]/navigation-menu:text-popover-foreground',
      'nx:group-data-[viewport=false]/navigation-menu:shadow-lg',
    ].join(' ');
    
    export const navigationMenuInlinePopoverTransitionClassName = [
      'nx:group-data-[viewport=false]/navigation-menu:transition-[opacity,scale]',
      'nx:group-data-[viewport=false]/navigation-menu:duration-fast',
      'nx:group-data-[viewport=false]/navigation-menu:ease-move',
      'nx:group-data-[viewport=false]/navigation-menu:data-[state=closed]:scale-95',
      'nx:group-data-[viewport=false]/navigation-menu:data-[state=closed]:opacity-0',
      'nx:group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-overlay-presence-exit',
    ].join(' ');
    
    // `animate-overlay-presence-exit` is a non-visual "presence bridge": Reka's
    // Presence only waits on `animationName`, not `transitionend`, so this inert
    // animation keeps a closing overlay mounted while the opacity/scale/translate
    // TRANSITIONS below run the visible exit. It animates an unread custom property (see
    // generateMotionUtilitiesCSS in @nxds/core), never a transitioned property, so it
    // cannot override those transitions.
    const overlayPresenceExitClassName =
      'nx:data-[state=closed]:animate-overlay-presence-exit';
    
    export const overlayFloatingTransitionClassName = [
      'nx:transition-[opacity,scale]',
      'nx:duration-fast',
      'nx:ease-move',
      'nx:data-[state=closed]:scale-95',
      'nx:data-[state=closed]:opacity-0',
      overlayPresenceExitClassName,
    ].join(' ');
    
    const overlayContentTransitionClassName = [
      'nx:transition-[opacity,scale]',
      'nx:duration-default',
      'nx:ease-move',
      'nx:data-[state=closed]:scale-95',
      'nx:data-[state=closed]:opacity-0',
      overlayPresenceExitClassName,
    ].join(' ');
    
    export const overlayScrimTransitionClassName = [
      'nx:transition-opacity',
      'nx:duration-default',
      'nx:ease-move',
      'nx:data-[state=closed]:opacity-0',
      overlayPresenceExitClassName,
    ].join(' ');
    
    export const overlayPanelTransitionClassName = [
      'nx:transition-[translate]',
      'nx:duration-slow',
      'nx:ease-move',
      overlayPresenceExitClassName,
    ].join(' ');
    
    export const overlayContentVariants = cva(
      [
        // Centred with margins, not a translate: a transform would make this the
        // containing block for fixed-position popups a dialog holds.
        'nx:fixed nx:inset-0 nx:z-modal nx:m-auto nx:flex nx:h-fit nx:w-full nx:max-w-lg nx:flex-col',
        'nx:max-h-[calc(100svh-2rem)] nx:overflow-hidden',
        'nx:gap-4 nx:border-default nx:border-border-default nx:bg-container nx:py-6 nx:shadow-lg',
        overlayContentTransitionClassName,
        'nx:sm:rounded-lg',
      ].join(' ')
    );
    
    export const overlayScrimVariants = cva(
      [
        'nx:fixed nx:inset-0 nx:z-modal nx:bg-overlay',
        overlayScrimTransitionClassName,
      ].join(' ')
    );
    
    export const overlayHeaderVariants = cva(
      'nx:flex nx:shrink-0 nx:flex-col nx:gap-1 nx:px-6',
      {
        variants: {
          variant: {
            default: 'nx:text-center nx:sm:text-left',
            center: 'nx:items-center nx:text-center',
          },
        },
        defaultVariants: {
          variant: 'default',
        },
      }
    );
    
    export const overlayBodyClassName = [
      'nx:min-h-0',
      'nx:overflow-y-auto',
      'nx:px-6',
      'nx:focus-visible:outline-2',
      'nx:focus-visible:outline-focus-default',
      'nx:focus-visible:[outline-offset:-2px]',
    ].join(' ');
    
    export const overlayFooterVariants = cva(
      'nx:flex nx:shrink-0 nx:gap-2 nx:px-6',
      {
        variants: {
          orientation: {
            horizontal:
              'nx:flex-col nx:sm:flex-row nx:sm:items-center nx:sm:justify-end',
            vertical: 'nx:flex-col nx:items-stretch nx:*:w-full',
          },
        },
        defaultVariants: {
          orientation: 'horizontal',
        },
      }
    );
    
    export const overlayCloseButtonClassName = [
      'nx:absolute nx:right-6 nx:top-6 nx:rounded-sm nx:border-0 nx:bg-transparent nx:p-1 nx:text-muted-foreground-subtle',
      'nx:transition-control',
      'nx:hover:bg-container-hover nx:hover:text-foreground',
      'nx:focus-visible:bg-container-hover nx:focus-visible:text-foreground',
      'nx:focus-visible:outline-2 nx:focus-visible:outline-focus-default',
      'nx:disabled:pointer-events-none',
    ].join(' ');
    
    export type OverlayHeaderVariants = VariantProps<typeof overlayHeaderVariants>;
    
    export type OverlayVariant = NonNullable<
      VariantProps<typeof overlayHeaderVariants>['variant']
    >;
    
    export type OverlayButtonOrientation = NonNullable<
      VariantProps<typeof overlayFooterVariants>['orientation']
    >;
    
    export type OverlayLayoutContextValue = {
      variant: OverlayVariant;
      buttonOrientation: OverlayButtonOrientation;
    };
    
    export const defaultOverlayLayout: OverlayLayoutContextValue = {
      variant: 'default',
      buttonOrientation: 'horizontal',
    };
    
    export function resolveOverlayButtonOrientation(
      variant: OverlayVariant
    ): OverlayButtonOrientation {
      return variant === 'center' ? 'vertical' : 'horizontal';
    }
    
  3. Keep each file at the path shown above it.

    Paths are relative to packages/vue/src/. Keep the same folders under your @/ root, and the imports between files work without edits.

Usage

import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/alert-dialog';

Examples

Destructive

Confirm a permanent delete with a destructive action button.

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

<template>
  <AlertDialog>
    <AlertDialogTrigger as-child>
      <Button variant="destructive">Delete account</Button>
    </AlertDialogTrigger>
    <AlertDialogContent>
      <AlertDialogHeader>
        <AlertDialogTitle>Delete your account?</AlertDialogTitle>
        <AlertDialogDescription
          >This action cannot be undone. This will permanently delete your
          account and remove your data from our servers.</AlertDialogDescription
        >
      </AlertDialogHeader>
      <AlertDialogFooter>
        <AlertDialogCancel>Cancel</AlertDialogCancel>
        <AlertDialogAction variant="destructive">Delete</AlertDialogAction>
      </AlertDialogFooter>
    </AlertDialogContent>
  </AlertDialog>
</template>

Centered

Center the header and stack full-width actions with variant="center".

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

<template>
  <AlertDialog>
    <AlertDialogTrigger as-child>
      <Button variant="destructive">Show centered dialog</Button>
    </AlertDialogTrigger>
    <AlertDialogContent variant="center">
      <AlertDialogHeader>
        <AlertDialogTitle>Dialog Title</AlertDialogTitle>
        <AlertDialogDescription
          >Make changes to your profile here. Click save when you are
          done.</AlertDialogDescription
        >
      </AlertDialogHeader>
      <AlertDialogFooter>
        <AlertDialogCancel>Cancel</AlertDialogCancel>
        <AlertDialogAction variant="destructive">Action</AlertDialogAction>
      </AlertDialogFooter>
    </AlertDialogContent>
  </AlertDialog>
</template>

Props

AlertDialogAction

AlertDialogAction props
PropTypeDefaultDescription
size"default" | "sm" | "lg" | "icon-sm" | "icon" | "icon-lg" | null"default"No description
variant"default" | "destructive" | "error" | "outline" | "dashed" | "secondary" | "ghost" | "link" | null"default"No description

AlertDialogBody

AlertDialogBody adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogCancel

AlertDialogCancel props
PropTypeDefaultDescription
size"default" | "sm" | "lg" | "icon-sm" | "icon" | "icon-lg" | null"default"No description
variant"default" | "destructive" | "error" | "outline" | "dashed" | "secondary" | "ghost" | "link" | null"outline"No description

AlertDialogContent

AlertDialogContent props
PropTypeDefaultDescription
variant"default" | "center""default"

Layout. default left-aligns the header and puts actions in a row. center centers the header and stacks full-width actions.

AlertDialogDescription

AlertDialogDescription adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogFooter adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogHeader

AlertDialogHeader props
PropTypeDefaultDescription
variant"default" | "center" | null"default"No description

AlertDialogOverlay

AlertDialogOverlay adds no props of its own; it accepts the props of the element or primitive it wraps.

AlertDialogTitle

AlertDialogTitle adds no props of its own; it accepts the props of the element or primitive it wraps.