soralabs
uibase-alert-dialogsource: ok · hit
Base Alert Dialog
An accessible alert dialog built on Base UI with smooth Motion 3D perspective entrance and exit animations, customizable sizes, and media slots.
npx shadcn add "https://ui.soralabs.studio/r/base-alert-dialog.json"free
Visual style
3d
Interaction
modal
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · mediumui-primitives@base-ui/react
animationmotion
iconslucide-react
Registry dependencies
utils@soralabs/hooks-use-controlled-state@soralabs/base-button
Files1 · source cached 10/8/2026, 5:57:08 PM
registry/ui/base/alert-dialog/index.tsx
"use client";
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import { useControlledState } from "@/hooks/use-controlled-state";
import { cn } from "@/lib/utils";
import { XIcon } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { type ComponentProps, createContext, useContext, useMemo } from "react";
import { Button } from "@/components/sora-ui/base/button";
interface AlertDialogContextValue {
open: boolean;
setOpen: (open: boolean) => void;
}
const AlertDialogContext = createContext<AlertDialogContextValue | null>(null);
function useAlertDialogContext() {
return useContext(AlertDialogContext);
}
type AlertDialogProps = ComponentProps<typeof AlertDialogPrimitive.Root>;
/**
* Root container for the Alert Dialog component powered by Base UI and Motion.
*/
function AlertDialog({
open: controlledOpen,
defaultOpen = false,
onOpenChange,
children,
...props
}: AlertDialogProps) {
const [open, setOpen] = useControlledState({
defaultValue: defaultOpen,
onChange: (nextOpen: boolean) => {
onOpenChange?.(
nextOpen,
undefined as unknown as AlertDialogPrimitive.Root.ChangeEventDetails
);
},
value: controlledOpen,
});
const contextValue = useMemo(
() => ({ open: Boolean(open), setOpen }),
[open, setOpen]
);
return (
<AlertDialogContext.Provider value={contextValue}>
<AlertDialogPrimitive.Root
onOpenChange={(nextOpen, eventDetails) => {
setOpen(nextOpen);
onOpenChange?.(nextOpen, eventDetails);
}}
open={open}
{...props}
>
{children}
</AlertDialogPrimitive.Root>
</AlertDialogContext.Provider>
);
}
type AlertDialogTriggerProps = ComponentProps<
typeof AlertDialogPrimitive.Trigger
>;
/**
* Trigger element that opens the alert dialog.
* Features Motion hover and tap scaling.
*/
function AlertDialogTrigger({
className,
render,
...props
}: AlertDialogTriggerProps) {
const prefersReducedMotion = useReducedMotion();
return (
<AlertDialogPrimitive.Trigger
className={className}
data-slot="alert-dialog-trigger"
render={
render ?? (
<motion.button
data-primary-action
type="button"
whileHover={prefersReducedMotion ? undefined : { scale: 1.05 }}
whileTap={prefersReducedMotion ? undefined : { scale: 0.95 }}
/>
)
}
{...props}
/>
);
}
type AlertDialogPortalProps = ComponentProps<
typeof AlertDialogPrimitive.Portal
>;
/**
* Portals alert dialog overlay and content to the document body.
*/
function AlertDialogPortal({ ...props }: AlertDialogPortalProps) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
);
}
type AlertDialogOverlayProps = ComponentProps<
typeof AlertDialogPrimitive.Backdrop
>;
/**
* Semi-transparent backdrop overlay behind the alert dialog with blur and fade transition.
*/
function AlertDialogOverlay({
className,
render,
...props
}: AlertDialogOverlayProps) {
const prefersReducedMotion = useReducedMotion();
return (
<AlertDialogPrimitive.Backdrop
className={cn(
"fixed inset-0 isolate z-50 bg-black/50 backdrop-blur-xs dark:bg-black/70",
className
)}
data-slot="alert-dialog-overlay"
render={
render ?? (
<motion.div
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
initial={{ opacity: 0 }}
transition={
prefersReducedMotion
? { duration: 0 }
: { duration: 0.2, ease: "easeOut" }
}
/>
)
}
{...props}
/>
);
}
type AlertDialogViewportProps = ComponentProps<
typeof AlertDialogPrimitive.Viewport
>;
/**
* Optional scroll viewport wrapper for overflowing alert dialog content.
*/
function AlertDialogViewport({
className,
...props
}: AlertDialogViewportProps) {
return (
<AlertDialogPrimitive.Viewport
className={cn(
"fixed inset-0 z-50 flex items-center justify-center overflow-y-auto p-4",
className
)}
data-slot="alert-dialog-viewport"
{...props}
/>
);
}
interface AlertDialogContentProps
extends ComponentProps<typeof AlertDialogPrimitive.Popup> {
/**
* Additional CSS class name for the fixed container wrapper.
*/
containerClassName?: string;
/**
* Additional CSS class name for the backdrop overlay.
*/
overlayClassName?: string;
/**
* Whether to render an optional top-right close icon button.
* @default false
*/
showCloseButton?: boolean;
/**
* Layout sizing variant of the alert dialog popup.
* @default "default"
*/
size?: "default" | "sm";
}
/**
* Modal alert dialog content container with Motion 3D perspective rotation and blur entrance/exit animations.
*/
function AlertDialogContent({
className,
children,
size = "default",
showCloseButton = false,
overlayClassName,
containerClassName,
render,
style,
...props
}: AlertDialogContentProps) {
const context = useAlertDialogContext();
const prefersReducedMotion = useReducedMotion();
const isOpen = context ? context.open : true;
return (
<AnimatePresence>
{isOpen && (
<AlertDialogPrimitive.Portal keepMounted>
<AlertDialogOverlay className={overlayClassName} />
<div
className={cn(
"pointer-events-none fixed inset-0 z-50 flex items-center justify-center p-4",
containerClassName
)}
>
<AlertDialogPrimitive.Popup
className={cn(
"group/alert-dialog-content pointer-events-auto relative grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl border border-border bg-background p-6 text-foreground text-sm shadow-2xl outline-none data-[size=sm]:max-w-xs data-[size=sm]:p-4 data-[size=default]:sm:max-w-lg data-[size=sm]:sm:max-w-sm",
className
)}
data-size={size}
data-slot="alert-dialog-content"
render={
render ?? (
<motion.div
animate={
prefersReducedMotion
? { opacity: 1 }
: {
opacity: 1,
filter: "blur(0px)",
rotateX: 0,
rotateY: 0,
z: 0,
transition: {
delay: 0.2,
duration: 0.5,
ease: [0.17, 0.67, 0.51, 1],
opacity: {
delay: 0.2,
duration: 0.5,
ease: "easeOut",
},
},
}
}
exit={
prefersReducedMotion
? { opacity: 0 }
: {
opacity: 0,
filter: "blur(10px)",
z: -100,
rotateY: 25,
rotateX: 5,
transformPerspective: 500,
transition: {
duration: 0.3,
ease: [0.67, 0.17, 0.62, 0.64],
},
}
}
initial={
prefersReducedMotion
? { opacity: 0 }
: {
opacity: 0,
filter: "blur(10px)",
z: -100,
rotateY: 25,
rotateX: 5,
transformPerspective: 500,
}
}
style={{
transformPerspective: 500,
transformStyle: "preserve-3d",
...style,
}}
/>
)
}
{...props}
>
{children}
{showCloseButton && (
<AlertDialogPrimitive.Close
aria-label="Close"
className="absolute top-4 right-4 inline-flex size-7 items-center justify-center rounded-md text-muted-foreground opacity-70 transition-opacity hover:bg-muted hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none"
data-slot="alert-dialog-close-button"
render={
<motion.button
type="button"
whileHover={
prefersReducedMotion ? undefined : { scale: 1.1 }
}
whileTap={
prefersReducedMotion ? undefined : { scale: 0.95 }
}
/>
}
>
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</AlertDialogPrimitive.Close>
)}
</AlertDialogPrimitive.Popup>
</div>
</AlertDialogPrimitive.Portal>
)}
</AnimatePresence>
);
}
type AlertDialogHeaderProps = ComponentProps<"div">;
/**
* Header section containing alert dialog title, description, and optional media slot.
*/
function AlertDialogHeader({ className, ...props }: AlertDialogHeaderProps) {
return (
<div
className={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]",
className
)}
data-slot="alert-dialog-header"
{...props}
/>
);
}
type AlertDialogMediaProps = ComponentProps<"div">;
/**
* Icon or media container positioned nicely within the alert dialog header.
*/
function AlertDialogMedia({ className, ...props }: AlertDialogMediaProps) {
return (
<div
className={cn(
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted text-foreground sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
className
)}
data-slot="alert-dialog-media"
{...props}
/>
);
}
type AlertDialogTitleProps = ComponentProps<typeof AlertDialogPrimitive.Title>;
/**
* Accessible title heading for the alert dialog.
*/
function AlertDialogTitle({ className, ...props }: AlertDialogTitleProps) {
return (
<AlertDialogPrimitive.Title
className={cn(
"font-semibold text-base text-foreground tracking-tight sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
className
)}
data-slot="alert-dialog-title"
{...props}
/>
);
}
type AlertDialogDescriptionProps = ComponentProps<
typeof AlertDialogPrimitive.Description
>;
/**
* Accessible description explaining the alert prompt or confirmation warning.
*/
function AlertDialogDescription({
className,
...props
}: AlertDialogDescriptionProps) {
return (
<AlertDialogPrimitive.Description
className={cn(
"text-balance text-muted-foreground text-sm sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2 md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
data-slot="alert-dialog-description"
{...props}
/>
);
}
type AlertDialogFooterProps = ComponentProps<"div">;
/**
* Footer section for alert dialog action buttons with responsive layouts.
*/
function AlertDialogFooter({ className, ...props }: AlertDialogFooterProps) {
return (
<div
className={cn(
"mt-2 flex flex-col-reverse gap-2 rounded-b-xl border-border border-t bg-muted/40 p-4 group-data-[size=default]/alert-dialog-content:-mx-6 group-data-[size=sm]/alert-dialog-content:-mx-4 group-data-[size=default]/alert-dialog-content:-mb-6 group-data-[size=sm]/alert-dialog-content:-mb-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
className
)}
data-slot="alert-dialog-footer"
{...props}
/>
);
}
interface AlertDialogActionProps extends ComponentProps<typeof Button> {
/**
* Whether clicking this action button should automatically close the alert dialog.
* @default false
*/
closeOnClick?: boolean;
}
/**
* Primary action button for the alert dialog.
*/
function AlertDialogAction({
className,
closeOnClick = false,
variant = "default",
...props
}: AlertDialogActionProps) {
if (closeOnClick) {
return (
<AlertDialogPrimitive.Close
data-slot="alert-dialog-action"
render={<Button className={className} variant={variant} {...props} />}
/>
);
}
return (
<Button
className={className}
data-slot="alert-dialog-action"
variant={variant}
{...props}
/>
);
}
type AlertDialogCancelProps = AlertDialogPrimitive.Close.Props &
Pick<ComponentProps<typeof Button>, "variant" | "size">;
/**
* Cancel or dismiss button that closes the alert dialog without proceeding.
*/
function AlertDialogCancel({
className,
variant = "outline",
size = "default",
...props
}: AlertDialogCancelProps) {
return (
<AlertDialogPrimitive.Close
className={className}
data-slot="alert-dialog-cancel"
render={<Button size={size} variant={variant} />}
{...props}
/>
);
}
type AlertDialogCloseProps = ComponentProps<typeof AlertDialogPrimitive.Close>;
/**
* Generic close button wrapper for closing the alert dialog.
*/
function AlertDialogClose({
className,
render,
...props
}: AlertDialogCloseProps) {
const prefersReducedMotion = useReducedMotion();
return (
<AlertDialogPrimitive.Close
className={className}
data-slot="alert-dialog-close"
render={
render ?? (
<motion.button
type="button"
whileHover={prefersReducedMotion ? undefined : { scale: 1.05 }}
whileTap={prefersReducedMotion ? undefined : { scale: 0.95 }}
/>
)
}
{...props}
/>
);
}
export type {
AlertDialogActionProps,
AlertDialogCancelProps,
AlertDialogCloseProps,
AlertDialogContentProps,
AlertDialogDescriptionProps,
AlertDialogFooterProps,
AlertDialogHeaderProps,
AlertDialogMediaProps,
AlertDialogOverlayProps,
AlertDialogPortalProps,
AlertDialogProps,
AlertDialogTitleProps,
AlertDialogTriggerProps,
AlertDialogViewportProps,
};
export {
AlertDialog,
AlertDialog as BaseAlertDialog,
AlertDialog as default,
AlertDialogAction,
AlertDialogCancel,
AlertDialogClose,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
AlertDialogViewport,
};