Skip to content

Alert Dialog

A modal confirmation that interrupts the user and needs an explicit answer: delete, sign out, discard. Focus starts on the safe choice, and clicking outside does nothing.

Installation

Add Alert Dialog to your project with the CK-UI CLI. It copies the source into your project, so the code is yours to change.

Terminal
npx ck-ui-cli@latest add alert-dialog
Adds
AlertDialog.tsxButton.tsxThemeScope.tsxto your CK-UI folder (src/components/ck-ui by default)
Installs
@radix-ui/react-dialog@radix-ui/react-slotclass-variance-authorityif your project does not have them yet

First time with CK-UI in this project? Run npx ck-ui-cli@latest init once before adding components. The installation guide explains what it sets up.

Manual installationInstall the packages and copy the files yourself.

Install the following dependencies

Run these commands in your terminal to install the required packages

Terminal
npm install @radix-ui/react-dialog @radix-ui/react-slot class-variance-authority

Add the Button component

AlertDialog imports ./Button, so add this file next to it

/components/ui/Button.tsx
import * as React from "react";
import Link from "next/link";
import { Slot } from "@radix-ui/react-slot";
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils";

// Server-compatible: no hooks or context, so Button can be rendered from server components.

export type ButtonVariant = "default" | "stroke" | "lighter" | "ghost";
export type ButtonColor = "primary" | "neutral" | "destructive" | "success";
export type ButtonSize = "xs" | "sm" | "md" | "lg";

/** @deprecated Use `primary` / `destructive`. */
type LegacyButtonColor = "blue" | "danger";
/** @deprecated Use `xs` / `sm` / `md` / `lg`. */
type LegacyButtonSize = "x-small" | "small" | "medium" | "large";

const LEGACY_COLORS: Record<LegacyButtonColor, ButtonColor> = { blue: "primary", danger: "destructive" };
const LEGACY_SIZES: Record<LegacyButtonSize, ButtonSize> = { "x-small": "xs", small: "sm", medium: "md", large: "lg" };

export function resolveButtonColor(color?: ButtonColor | LegacyButtonColor | null): ButtonColor {
    if (!color) return "primary";
    return color in LEGACY_COLORS ? LEGACY_COLORS[color as LegacyButtonColor] : (color as ButtonColor);
}

export function resolveButtonSize(size?: ButtonSize | LegacyButtonSize | null): ButtonSize {
    if (!size) return "md";
    return size in LEGACY_SIZES ? LEGACY_SIZES[size as LegacyButtonSize] : (size as ButtonSize);
}

const buttonRootVariants = cva(
    [
        "group/button relative flex items-center justify-center whitespace-nowrap rounded-lg border size-label-sm",
        "transition-colors duration-base ease-standard focus-ring",
        "disabled:cursor-not-allowed disabled:border-transparent disabled:bg-muted disabled:text-muted-foreground",
        "aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:border-transparent aria-disabled:bg-muted aria-disabled:text-muted-foreground",
    ],
    {
        variants: {
            variant: {
                default: "border-transparent",
                stroke: "bg-transparent hover:border-transparent",
                lighter: "border-transparent",
                ghost: "border-transparent bg-transparent",
            },
            color: {
                primary: "",
                neutral: "",
                destructive: "",
                success: "",
            },
            size: {
                lg: "h-10 min-w-10 gap-3 px-3",
                md: "h-9 min-w-9 gap-2.5 px-2.5",
                sm: "h-8 min-w-8 gap-2 px-2",
                xs: "h-7 min-w-7 gap-2 px-1.5",
            },
        },
        defaultVariants: {
            variant: "default",
            color: "primary",
            size: "md",
        },
        compoundVariants: [
            // Default (filled)
            { variant: "default", color: "primary", class: "bg-primary text-primary-foreground hover:bg-primary-hover" },
            { variant: "default", color: "neutral", class: "bg-secondary text-secondary-foreground hover:bg-secondary-hover" },
            { variant: "default", color: "destructive", class: "bg-destructive text-destructive-foreground hover:bg-destructive-hover" },
            { variant: "default", color: "success", class: "bg-success text-success-foreground hover:bg-success-hover" },

            // Stroke, lighter and ghost colour their text with the -strong shade (AA on background and -subtle).
            { variant: "stroke", color: "primary", class: "border-primary-border text-primary-strong hover:bg-primary-subtle" },
            { variant: "stroke", color: "neutral", class: "border-border text-muted-foreground hover:bg-muted hover:text-foreground" },
            { variant: "stroke", color: "destructive", class: "border-destructive-border text-destructive-strong hover:bg-destructive-subtle" },
            { variant: "stroke", color: "success", class: "border-success-border text-success-strong hover:bg-success-subtle" },

            // Lighter
            { variant: "lighter", color: "primary", class: "bg-primary-subtle text-primary-strong hover:border-primary-border hover:bg-transparent" },
            { variant: "lighter", color: "neutral", class: "bg-muted text-muted-foreground hover:border-border hover:bg-transparent hover:text-foreground" },
            { variant: "lighter", color: "destructive", class: "bg-destructive-subtle text-destructive-strong hover:border-destructive-border hover:bg-transparent" },
            { variant: "lighter", color: "success", class: "bg-success-subtle text-success-strong hover:border-success-border hover:bg-transparent" },

            // Ghost
            { variant: "ghost", color: "primary", class: "text-primary-strong hover:bg-primary-subtle" },
            { variant: "ghost", color: "neutral", class: "text-muted-foreground hover:bg-muted hover:text-foreground" },
            { variant: "ghost", color: "destructive", class: "text-destructive-strong hover:bg-destructive-subtle" },
            { variant: "ghost", color: "success", class: "text-success-strong hover:bg-success-subtle" },
        ],
    }
);

// Without an explicit size, Button.Icon follows the parent Button's data-size (pure CSS, works with asChild).
const buttonIconVariants = cva("flex shrink-0 items-center justify-center", {
    variants: {
        size: {
            inherit: "size-5 group-data-[size=sm]/button:size-4 group-data-[size=xs]/button:size-4",
            lg: "size-5",
            md: "size-5",
            sm: "size-4",
            xs: "size-4",
        },
    },
    defaultVariants: {
        size: "inherit",
    },
});

export interface ButtonRootProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color"> {
    /** Render the single child element instead of a <button>, merging Button's props onto it. */
    asChild?: boolean;
    variant?: ButtonVariant;
    color?: ButtonColor | LegacyButtonColor;
    size?: ButtonSize | LegacyButtonSize;
    /** Disables the button and marks it busy while an action runs. */
    loading?: boolean;
    /** Render a Next.js link styled as a button. */
    href?: string;
}

function LoadingIndicator() {
    return (
        <svg className="size-4 shrink-0 animate-spin" viewBox="0 0 24 24" fill="none" aria-hidden="true">
            <circle cx="12" cy="12" r="9" stroke="currentColor" strokeOpacity="0.25" strokeWidth="3" />
            <path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
        </svg>
    );
}

const ButtonRoot = React.forwardRef<HTMLButtonElement, ButtonRootProps>(
    ({ asChild = false, className, variant, color, size, loading = false, href, disabled, type, children, ...props }, ref) => {
        const resolvedSize = resolveButtonSize(size);
        const classes = cn(buttonRootVariants({ variant, color: resolveButtonColor(color), size: resolvedSize }), className);
        const isDisabled = Boolean(disabled || loading);

        if (asChild) {
            return (
                <Slot ref={ref} className={classes} data-size={resolvedSize} aria-disabled={isDisabled || undefined} aria-busy={loading || undefined} {...props}>
                    {children}
                </Slot>
            );
        }

        if (href !== undefined) {
            const anchorProps = props as React.AnchorHTMLAttributes<HTMLAnchorElement>;
            const anchorRef = ref as unknown as React.Ref<HTMLAnchorElement>;

            // A disabled link has no href, so it cannot be followed or focused.
            if (isDisabled) {
                return (
                    <a ref={anchorRef} className={classes} data-size={resolvedSize} aria-disabled="true" aria-busy={loading || undefined} {...anchorProps}>
                        {loading && <LoadingIndicator />}
                        {children}
                    </a>
                );
            }

            return (
                <Link ref={anchorRef} href={href} className={classes} data-size={resolvedSize} {...anchorProps}>
                    {children}
                </Link>
            );
        }

        return (
            <button ref={ref} type={type ?? "button"} className={classes} data-size={resolvedSize} disabled={isDisabled} aria-busy={loading || undefined} {...props}>
                {loading && <LoadingIndicator />}
                {children}
            </button>
        );
    }
);
ButtonRoot.displayName = "Button.Root";

export interface ButtonIconProps extends React.HTMLAttributes<HTMLElement> {
    size?: ButtonSize | LegacyButtonSize;
    /** Element or icon component to render, e.g. `as={RiAddLine}`. */
    as?: React.ElementType;
}

const ButtonIcon = React.forwardRef<HTMLElement, ButtonIconProps>(({ as: Component = "span", className, size, ...props }, ref) => {
    return <Component ref={ref} aria-hidden="true" className={cn(buttonIconVariants({ size: size ? resolveButtonSize(size) : "inherit" }), className)} {...props} />;
});
ButtonIcon.displayName = "Button.Icon";

const Button = {
    Root: ButtonRoot,
    Icon: ButtonIcon,
};

export { Button, ButtonRoot as Root, ButtonIcon as Icon, buttonRootVariants, buttonIconVariants };
export default Button;

Add the ThemeScope component

AlertDialog imports ./ThemeScope, so add this file next to it

/components/ui/ThemeScope.tsx
"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

/*
 * A local light/dark boundary. Everything inside renders in `theme`, whatever the site theme is: the semantic tokens
 * (`bg-background`, `text-foreground`, …) and every `dark:` utility resolve to the nearest boundary, not to <html>.
 * Overlays that portal out of the tree (Select, Popover, Tooltip, Dropdown, Modal, AlertDialog, Drawer, Toast) render
 * into a layer that carries the same theme, so a menu opened inside a dark scope is dark too.
 *
 * It never touches <html>, the site's theme state or its storage. Without `theme` it renders no theme class and
 * follows the surrounding theme. Nest at most one scope below the site theme (see `darkMode` in tailwind.config.ts).
 */

export type ScopeTheme = "light" | "dark";

const PortalContainerContext = React.createContext<HTMLElement | null>(null);

/** Where overlays portal to: the nearest themed ThemeScope's layer, or null for the default (document.body). */
export function usePortalContainer() {
    return React.useContext(PortalContainerContext);
}

// A themed layer at the end of <body> for portalled overlays. Only created while the scope sets a theme, so a scope
// that follows the site leaves the document exactly as it was.
function usePortalLayer(theme: ScopeTheme | undefined) {
    const [layer, setLayer] = React.useState<HTMLElement | null>(null);
    const themed = theme !== undefined;

    React.useEffect(() => {
        if (!themed) return;
        const element = document.createElement("div");
        element.setAttribute("data-theme-scope-layer", "");
        document.body.appendChild(element);
        setLayer(element);
        return () => {
            element.remove();
            setLayer(null);
        };
    }, [themed]);

    React.useEffect(() => {
        if (!layer || !theme) return;
        // text-foreground: portalled text that sets no colour of its own resolves against the layer's tokens.
        layer.className = `${theme} text-foreground`;
    }, [layer, theme]);

    return layer;
}

export interface RootProps extends React.HTMLAttributes<HTMLDivElement> {
    /** Theme for this subtree. Leave undefined to follow the surrounding (site) theme. */
    theme?: ScopeTheme;
}

const Root = React.forwardRef<HTMLDivElement, RootProps>(({ theme, className, children, ...props }, ref) => {
    const layer = usePortalLayer(theme);
    // A scope that follows its surroundings keeps the surrounding scope's layer.
    const outerLayer = usePortalContainer();

    return (
        <div ref={ref} data-theme-scope={theme} className={cn(theme && [theme, "text-foreground"], className)} {...props}>
            <PortalContainerContext.Provider value={theme ? layer : outerLayer}>{children}</PortalContainerContext.Provider>
        </div>
    );
});
Root.displayName = "ThemeScope.Root";

export { Root };

Create or update your AlertDialog component

Copy this code to create your AlertDialog component

/components/ui/AlertDialog.tsx
"use client";

import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { cn } from "@/lib/utils";
import * as Button from "./Button";
import { usePortalContainer } from "./ThemeScope";

/*
 * A modal confirmation that interrupts the user and needs an explicit answer.
 * Built on the Radix Dialog primitive the same way Radix's own AlertDialog is:
 *   role="alertdialog", always modal (focus trap, scroll lock, inert background),
 *   no dismissal by clicking outside, Escape cancels, focus starts on AlertDialog.Cancel
 *   (the safe choice) and returns to the trigger on close.
 */

type AlertDialogContentContextValue = {
    cancelRef: React.MutableRefObject<HTMLButtonElement | null>;
};

const AlertDialogContentContext = React.createContext<AlertDialogContentContextValue | null>(null);

function setRef<T>(ref: React.Ref<T> | undefined, value: T) {
    if (typeof ref === "function") ref(value);
    else if (ref) (ref as React.MutableRefObject<T>).current = value;
}

export type AlertDialogRootProps = Omit<DialogPrimitive.DialogProps, "modal">;

/** Works controlled (`open` + `onOpenChange`) or uncontrolled (`defaultOpen`, `AlertDialog.Trigger`). Always modal. */
const Root = (props: AlertDialogRootProps) => <DialogPrimitive.Root {...props} modal />;
Root.displayName = "AlertDialog.Root";

const Trigger = React.forwardRef<HTMLButtonElement, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>>((props, ref) => (
    <DialogPrimitive.Trigger ref={ref} {...props} />
));
Trigger.displayName = "AlertDialog.Trigger";

export type AlertDialogSize = "sm" | "md" | "lg";

const sizeClasses: Record<AlertDialogSize, string> = {
    sm: "max-w-sm",
    md: "max-w-md",
    lg: "max-w-lg",
};

export interface AlertDialogContentProps
    extends Omit<React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>, "onPointerDownOutside" | "onInteractOutside" | "forceMount" | "asChild"> {
    size?: AlertDialogSize;
}

/**
 * The dialog panel (role="alertdialog"), rendered in a portal over a backdrop.
 * Needs an AlertDialog.Title and an AlertDialog.Description.
 */
const Content = React.forwardRef<HTMLDivElement, AlertDialogContentProps>(({ size = "md", className, children, onOpenAutoFocus, onCloseAutoFocus, ...props }, ref) => {
    const cancelRef = React.useRef<HTMLButtonElement | null>(null);
    const openerRef = React.useRef<HTMLElement | null>(null);
    const context = React.useMemo(() => ({ cancelRef }), []);
    const container = usePortalContainer();

    return (
        <DialogPrimitive.Portal container={container}>
            {/* The overlay doubles as the scroll container so tall content stays reachable on small screens. */}
            <DialogPrimitive.Overlay
                className={cn(
                    "fixed inset-0 z-modal grid place-items-center overflow-y-auto p-4",
                    "bg-overlay backdrop-blur-sm",
                    "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0"
                )}
            >
                <DialogPrimitive.Content
                    ref={ref}
                    {...props}
                    role="alertdialog"
                    onOpenAutoFocus={(event) => {
                        // Remember what had focus so closing returns there, also when the dialog is opened without an
                        // AlertDialog.Trigger (controlled `open`), where Radix has no trigger to return to.
                        const active = document.activeElement;
                        openerRef.current = active instanceof HTMLElement && active !== document.body ? active : null;
                        onOpenAutoFocus?.(event);
                        if (event.defaultPrevented || !cancelRef.current) return;
                        // Start on the safe answer. Without a Cancel, Radix focuses the first focusable element.
                        event.preventDefault();
                        cancelRef.current.focus({ preventScroll: true });
                    }}
                    onCloseAutoFocus={(event) => {
                        onCloseAutoFocus?.(event);
                        const opener = openerRef.current;
                        if (event.defaultPrevented || !opener?.isConnected) return;
                        event.preventDefault();
                        opener.focus({ preventScroll: true });
                    }}
                    // An alert dialog needs an explicit answer: clicks outside do nothing.
                    onPointerDownOutside={(event) => event.preventDefault()}
                    onInteractOutside={(event) => event.preventDefault()}
                    className={cn(
                        "relative w-full rounded-20 border border-border bg-background p-6 text-foreground shadow-medium",
                        "data-[state=open]:animate-in data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:zoom-out-95",
                        sizeClasses[size],
                        className
                    )}
                >
                    <AlertDialogContentContext.Provider value={context}>{children}</AlertDialogContentContext.Provider>
                </DialogPrimitive.Content>
            </DialogPrimitive.Overlay>
        </DialogPrimitive.Portal>
    );
});
Content.displayName = "AlertDialog.Content";

const Header = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
    <div ref={ref} className={cn("flex flex-col gap-2", className)} {...props} />
));
Header.displayName = "AlertDialog.Header";

/** Holds Cancel and Action. Stacks full-width on small screens (Action on top), right-aligned from `sm`. */
const Footer = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
    <div ref={ref} className={cn("mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end [&>*]:w-full sm:[&>*]:w-auto", className)} {...props} />
));
Footer.displayName = "AlertDialog.Footer";

const Title = React.forwardRef<HTMLHeadingElement, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>>(({ className, ...props }, ref) => (
    <DialogPrimitive.Title ref={ref} className={cn("size-label-lg text-foreground", className)} {...props} />
));
Title.displayName = "AlertDialog.Title";

const Description = React.forwardRef<HTMLParagraphElement, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>>(({ className, ...props }, ref) => (
    <DialogPrimitive.Description ref={ref} className={cn("size-p-sm text-muted-foreground", className)} {...props} />
));
Description.displayName = "AlertDialog.Description";

export interface AlertDialogCancelProps extends Omit<Button.ButtonRootProps, "href" | "variant" | "color"> {
    /** Render your own element (it receives the close behaviour and focus) instead of the default button. */
    asChild?: boolean;
}

/** Closes without acting. Receives initial focus. */
const Cancel = React.forwardRef<HTMLButtonElement, AlertDialogCancelProps>(({ asChild = false, size, loading, children, ...props }, forwardedRef) => {
    const context = React.useContext(AlertDialogContentContext);
    const ref = React.useCallback(
        (node: HTMLButtonElement | null) => {
            if (context) context.cancelRef.current = node;
            setRef(forwardedRef, node);
        },
        [context, forwardedRef]
    );

    if (asChild) {
        return (
            <DialogPrimitive.Close ref={ref} asChild {...props}>
                {children}
            </DialogPrimitive.Close>
        );
    }

    return (
        <DialogPrimitive.Close asChild>
            <Button.Root ref={ref} variant="stroke" color="neutral" size={size} loading={loading} {...props}>
                {children}
            </Button.Root>
        </DialogPrimitive.Close>
    );
});
Cancel.displayName = "AlertDialog.Cancel";

export interface AlertDialogActionProps extends Omit<Button.ButtonRootProps, "href" | "variant" | "color"> {
    /** `destructive` for irreversible actions (delete, remove, revoke). */
    color?: "primary" | "destructive";
    asChild?: boolean;
}

/**
 * Confirms and closes. To keep the dialog open while an async action runs, call `event.preventDefault()`
 * in `onClick` and close it yourself through the controlled `open` prop.
 */
const Action = React.forwardRef<HTMLButtonElement, AlertDialogActionProps>(({ asChild = false, color = "primary", size, loading, children, ...props }, ref) => {
    if (asChild) {
        return (
            <DialogPrimitive.Close ref={ref} asChild {...props}>
                {children}
            </DialogPrimitive.Close>
        );
    }

    return (
        <DialogPrimitive.Close asChild>
            <Button.Root ref={ref} variant="default" color={color} data-color={color} size={size} loading={loading} {...props}>
                {children}
            </Button.Root>
        </DialogPrimitive.Close>
    );
});
Action.displayName = "AlertDialog.Action";

export { Root, Trigger, Content, Header, Footer, Title, Description, Cancel, Action };

Test Your Component

  • 1. Add the component to a test page
  • 2. Check all props and variants work as expected
  • 3. Verify responsive behavior
  • 4. Test with different content/data
  • 5. Ensure accessibility features work correctly

Sign Out

A non-destructive confirmation uses the default (primary) action color.

Irreversible Action

For actions that cannot be undone, ask the user to type the name of what will be deleted before the action is enabled. Focus still starts on Cancel.

Controlled

Control open to keep the dialog up while a request runs: call event.preventDefault() in the action's onClick, then close it yourself when the request finishes.

  • Use Alert Dialog when the user must answer before continuing (confirmations, destructive actions). It has role alertdialog, cannot be dismissed by clicking outside and focuses Cancel first.
  • Use Modal for tasks and content (forms, details, settings). It can be dismissed by clicking outside and has more layout options.

Accessibility

  • The panel has role="alertdialog", is named by AlertDialog.Title and described by AlertDialog.Description. Both are required.
  • Focus moves to AlertDialog.Cancel when it opens, so pressing Enter by reflex never confirms a destructive action. Without a Cancel, the first focusable element is focused.
  • Tab and Shift+Tab stay inside the dialog. Escape closes it like Cancel. Focus returns to the trigger when it closes.
  • It is always modal: page scrolling is locked and the rest of the page is hidden from assistive technology while it is open.
  • Clicks on the backdrop are ignored; the user has to choose an answer.
  • Destructive actions use color="destructive" and a label that names the action ("Delete project"), not "OK".

API Reference

Built on the Radix UI Dialog primitive with the same behaviour as Radix Alert Dialog. Import with import * as AlertDialog from "@/components/ui/AlertDialog".

AlertDialog.Root

Holds the open state. Always modal.

See Radix UI docs for base props.

PropTypeDefaultDescription
open"boolean""undefined"

Controlled open state.

defaultOpen"boolean""false"

Initial open state when uncontrolled.

onOpenChange"(open: boolean) => void""undefined"

Called when the dialog opens or closes.

AlertDialog.Trigger

Opens the dialog.

See Radix UI docs for base props.

PropTypeDefaultDescription
asChild"boolean""false"

Render the single child element instead, merging this part's props and behaviour onto it.

className"string""undefined"

Additional classes, merged last.

AlertDialog.Content

The panel (role alertdialog), rendered in a portal over a backdrop that also scrolls tall content.

PropTypeDefaultDescription
size
"sm""md""lg"
"md"

Maximum width.

onOpenAutoFocus"(event: Event) => void""undefined"

Call event.preventDefault() to focus something other than Cancel yourself.

onEscapeKeyDown"(event: KeyboardEvent) => void""undefined"

Call event.preventDefault() to keep the dialog open on Escape.

className"string""undefined"

Classes for the panel.

AlertDialog.Header

Stacks the title and description.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

AlertDialog.Title

Names the dialog. Required.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

AlertDialog.Description

Describes the consequence. Required.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

Holds Cancel and Action: stacked full-width on small screens (action on top), right-aligned from the sm breakpoint.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

AlertDialog.Cancel

Closes without acting and receives focus on open. Renders a neutral stroke Button.

PropTypeDefaultDescription
asChild"boolean""false"

Use your own element; it still closes the dialog and receives initial focus.

size
"xs""sm""md""lg"
"md"

Button size.

disabled"boolean""false"

Disables the button.

className"string""undefined"

Additional classes, merged last.

AlertDialog.Action

Confirms and closes. Renders a filled Button.

PropTypeDefaultDescription
color
"primary""destructive"
"primary"

destructive for irreversible actions.

loading"boolean""false"

Shows a spinner, disables the button and sets aria-busy.

onClick"(event) => void""undefined"

Call event.preventDefault() to keep the dialog open.

asChild"boolean""false"

Render the single child element instead, merging this part's props and behaviour onto it.

className"string""undefined"

Additional classes, merged last.

Used in these blocks

Block families with at least one block built on this component. Each page previews its blocks and lists the components and packages they use.

Application