A modal dialog that interrupts the user with important content and expects a response.
Installation
Install the dependencies
pnpm add cn class-variance-authority github:sveltery/baseCopy the component
Copy the following files into src/lib/components/ui/alert-dialog.
It imports the button component, so copy its folder too.
<script lang="ts">
import { cn } from 'cn';
import { Button, type ButtonProps } from '../button/index.js';
let { children, class: classProp, ...props }: ButtonProps = $props();
</script>
<Button data-slot="alert-dialog-action" {children} {...props} class={cn('', classProp)} /><script lang="ts">
import type { ComponentProps, Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import { AlertDialog as Primitive } from '@sveltery/base/alert-dialog';
import { cn } from 'cn';
import { Button, type ButtonProps } from '../button/index.js';
let { class: classProp, variant = 'outline', size = 'default', ...props }: ComponentProps<typeof Primitive.Close> & Pick<ButtonProps, 'variant' | 'size'> = $props();
</script>
{#snippet button(buttonProps: HTMLAttributes<HTMLElement>, _state: unknown, content: Snippet | undefined)}
<Button {variant} {size} {...buttonProps}>{@render content?.()}</Button>
{/snippet}
<Primitive.Close data-slot="alert-dialog-cancel" class={cn('', classProp)} render={button} {...props} /><script lang="ts">
import type { ComponentProps } from 'svelte';
import { AlertDialog as Primitive } from '@sveltery/base/alert-dialog';
import { cn } from 'cn';
import AlertDialogPortal from './AlertDialogPortal.svelte';
import AlertDialogOverlay from './AlertDialogOverlay.svelte';
let { children, class: classProp, size = 'default', ...props }: ComponentProps<typeof Primitive.Popup> & { size?: 'default' | 'sm' } = $props();
</script>
<AlertDialogPortal>
<AlertDialogOverlay />
<Primitive.Popup data-slot="alert-dialog-content" data-size={size} {children} {...props} class={cn('data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/10 gap-4 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none', classProp)} />
</AlertDialogPortal><script lang="ts">
import type { ComponentProps } from 'svelte';
import { AlertDialog as Primitive } from '@sveltery/base/alert-dialog';
import { cn } from 'cn';
let { children, class: classProp, ...props }: ComponentProps<typeof Primitive.Description> = $props();
</script>
<Primitive.Description data-slot="alert-dialog-description" {children} {...props} class={cn('text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3', classProp)} /><script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from 'cn';
let { children, class: classProp, ...props }: HTMLAttributes<HTMLDivElement> = $props();
</script>
<div data-slot="alert-dialog-footer" {...props} class={cn('bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end', classProp)}>
{@render children?.()}
</div><script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from 'cn';
let { children, class: classProp, ...props }: HTMLAttributes<HTMLDivElement> = $props();
</script>
<div data-slot="alert-dialog-header" {...props} class={cn('grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]', classProp)}>
{@render children?.()}
</div><script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from 'cn';
let { children, class: classProp, ...props }: HTMLAttributes<HTMLDivElement> = $props();
</script>
<div data-slot="alert-dialog-media" {...props} class={cn("bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6", classProp)}>
{@render children?.()}
</div><script lang="ts">
import type { ComponentProps } from 'svelte';
import { AlertDialog as Primitive } from '@sveltery/base/alert-dialog';
import { cn } from 'cn';
let { children, class: classProp, ...props }: ComponentProps<typeof Primitive.Backdrop> = $props();
</script>
<Primitive.Backdrop data-slot="alert-dialog-overlay" {children} {...props} class={cn('data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50', classProp)} /><script lang="ts">
import type { ComponentProps } from 'svelte';
import { AlertDialog as Primitive } from '@sveltery/base/alert-dialog';
let { children, ...props }: ComponentProps<typeof Primitive.Portal> = $props();
</script>
<Primitive.Portal data-slot="alert-dialog-portal" {...props}>
{@render children?.()}
</Primitive.Portal><script lang="ts">
import type { ComponentProps } from 'svelte';
import { AlertDialog as Primitive } from '@sveltery/base/alert-dialog';
import { cn } from 'cn';
let { children, class: classProp, ...props }: ComponentProps<typeof Primitive.Title> = $props();
</script>
<Primitive.Title data-slot="alert-dialog-title" {children} {...props} class={cn('text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2', classProp)} /><script lang="ts">
import type { ComponentProps } from 'svelte';
import { AlertDialog as Primitive } from '@sveltery/base/alert-dialog';
let { children, ...props }: ComponentProps<typeof Primitive.Trigger> = $props();
</script>
<Primitive.Trigger data-slot="alert-dialog-trigger" {children} {...props} />import { AlertDialog as Primitive } from '@sveltery/base/alert-dialog';
export const AlertDialog = Primitive.Root as typeof Primitive.Root;
export { default as AlertDialogTrigger } from './AlertDialogTrigger.svelte';
export { default as AlertDialogPortal } from './AlertDialogPortal.svelte';
export { default as AlertDialogOverlay } from './AlertDialogOverlay.svelte';
export { default as AlertDialogContent } from './AlertDialogContent.svelte';
export { default as AlertDialogHeader } from './AlertDialogHeader.svelte';
export { default as AlertDialogFooter } from './AlertDialogFooter.svelte';
export { default as AlertDialogMedia } from './AlertDialogMedia.svelte';
export { default as AlertDialogTitle } from './AlertDialogTitle.svelte';
export { default as AlertDialogDescription } from './AlertDialogDescription.svelte';
export { default as AlertDialogAction } from './AlertDialogAction.svelte';
export { default as AlertDialogCancel } from './AlertDialogCancel.svelte';Update the import paths
Change the imports to match your project setup.
Usage
<script lang="ts">
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '$lib/components/ui/alert-dialog';
</script><AlertDialog>
<AlertDialogTrigger>Show Dialog</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>This action cannot be undone. It permanently deletes your account.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>Closing
An alert dialog is always modal, and a press outside it does not close it. AlertDialogCancel and the Escape key close it. AlertDialogAction is a Button and leaves the dialog open, so that you can run the action first. Use bind:open to close it from your code:
<script lang="ts">
let open = $state(false);
</script>
<AlertDialog bind:open>
…
<AlertDialogAction onclick={async () => { await deleteProject(); open = false; }}>Delete</AlertDialogAction>
</AlertDialog>Size
size="sm" on AlertDialogContent makes a narrow dialog with the footer buttons side by side.
API reference
AlertDialog is styled on top of the Sveltery Base AlertDialog primitive, which owns its behavior, focus handling and accessibility. See the Base UI Alert Dialog documentation for the behavior it ports.