Remocn
componentalert-dialogsource: ok · refreshed
UI Alert Dialog
A modal alert dialog whose opened/closed state is a pure function of the timeline; the backdrop dim and popup zoom are keyframed presets. Self-contained — pair it with the Button as a trigger (see the example).
npx shadcn add "https://www.remocn.dev/r/alert-dialog.json"free
Where it goes
stats
Visual style
playful
Interaction
animated-countermodal
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightremotion
Registry dependencies
@remocn/remocn-ui
Files2 · source cached 10/7/2026, 11:35:32 PM
registry/remocn-ui/alert-dialog/index.tsx
"use client";
import { type RemocnTheme, useRemocnTheme } from "@/lib/remocn-ui";
export type AlertDialogState = "closed" | "opened";
export interface AlertDialogProps {
state?: AlertDialogState;
style?: AlertDialogStyle;
title?: string;
description?: string;
actionLabel?: string;
cancelLabel?: string;
theme?: Partial<RemocnTheme>;
className?: string;
}
const POPUP_WIDTH = 400;
const MAX_OVERLAY_ALPHA = 0.5;
export interface AlertDialogStyle {
overlayOpacity: number;
popupOpacity: number;
popupScale: number;
popupTranslateY: number;
}
export interface AlertDialogStyleContext {
popoverBg: string;
popoverFg: string;
mutedFg: string;
border: string;
radius: number;
actionBg: string;
actionFg: string;
cancelFg: string;
}
export function alertDialogStyleContext(
theme: RemocnTheme,
): AlertDialogStyleContext {
return {
popoverBg: theme.popover,
popoverFg: theme.popoverForeground,
mutedFg: theme.mutedForeground,
border: theme.border,
radius: theme.radius,
actionBg: theme.destructive,
actionFg: theme.destructiveForeground,
cancelFg: theme.foreground,
};
}
export function alertDialogStyle(
state: AlertDialogState,
_ctx: AlertDialogStyleContext,
): AlertDialogStyle {
switch (state) {
case "opened":
return {
overlayOpacity: 1,
popupOpacity: 1,
popupScale: 1,
popupTranslateY: 0,
};
default:
return {
overlayOpacity: 0,
popupOpacity: 0,
popupScale: 0.95,
popupTranslateY: 8,
};
}
}
export function AlertDialog({
state = "closed",
style,
title = "Delete account?",
description = "This action cannot be undone. This will permanently remove your data from our servers.",
actionLabel = "Delete",
cancelLabel = "Cancel",
theme: themeOverride,
className,
}: AlertDialogProps) {
const theme = useRemocnTheme(themeOverride, "light");
const ctx = alertDialogStyleContext(theme);
const v = style ?? alertDialogStyle(state, ctx);
const buttonBase: React.CSSProperties = {
height: 40,
padding: "0 20px",
fontSize: 15,
fontWeight: 500,
letterSpacing: "-0.01em",
borderRadius: ctx.radius,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
};
return (
<div
style={{
position: "absolute",
inset: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontFamily:
"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif",
}}
>
{}
<div
style={{
position: "absolute",
inset: 0,
background: `rgba(0, 0, 0, ${MAX_OVERLAY_ALPHA * v.overlayOpacity})`,
}}
/>
<div
className={className}
style={{
position: "relative",
width: POPUP_WIDTH,
transform: `translateY(${v.popupTranslateY}px) scale(${v.popupScale})`,
opacity: v.popupOpacity,
background: ctx.popoverBg,
color: ctx.popoverFg,
border: `1px solid ${ctx.border}`,
borderRadius: ctx.radius + 6,
padding: 24,
display: "flex",
flexDirection: "column",
gap: 8,
boxShadow: "0 24px 48px -12px rgba(0,0,0,0.25)",
}}
>
<div
style={{ fontSize: 18, fontWeight: 500, letterSpacing: "-0.01em" }}
>
{title}
</div>
<div style={{ fontSize: 14, lineHeight: 1.5, color: ctx.mutedFg }}>
{description}
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
gap: 8,
marginTop: 16,
}}
>
{}
<button
type="button"
style={{
...buttonBase,
background: "transparent",
color: ctx.cancelFg,
border: `1px solid ${ctx.border}`,
}}
>
{cancelLabel}
</button>
{}
<button
type="button"
style={{
...buttonBase,
background: ctx.actionBg,
color: ctx.actionFg,
border: "1px solid transparent",
}}
>
{actionLabel}
</button>
</div>
</div>
</div>
);
}
registry/remocn-ui/alert-dialog/use-alert-dialog-transition.ts
"use client";
import {
type AlertDialogState,
type AlertDialogStyle,
alertDialogStyle,
alertDialogStyleContext,
} from "@/components/remocn/alert-dialog";
import {
easings,
type RemocnTheme,
type Step,
useRemocnTheme,
useStateTransition,
} from "@/lib/remocn-ui";
export const DEFAULT_DURATION = 12;
export function tweenAlertDialogStyle(
a: AlertDialogStyle,
b: AlertDialogStyle,
t: number,
): AlertDialogStyle {
return {
overlayOpacity:
a.overlayOpacity + (b.overlayOpacity - a.overlayOpacity) * t,
popupOpacity: a.popupOpacity + (b.popupOpacity - a.popupOpacity) * t,
popupScale: a.popupScale + (b.popupScale - a.popupScale) * t,
popupTranslateY:
a.popupTranslateY + (b.popupTranslateY - a.popupTranslateY) * t,
};
}
export interface AlertDialogTransitionOptions {
theme?: Partial<RemocnTheme>;
mode?: "light" | "dark";
speed?: number;
defaultDuration?: number;
}
export function useAlertDialogTransition(
steps: Step<AlertDialogState>[],
opts: AlertDialogTransitionOptions = {},
): AlertDialogStyle {
const {
theme: themeOverride,
mode,
speed = 1,
defaultDuration = DEFAULT_DURATION,
} = opts;
const theme = useRemocnTheme(themeOverride, mode);
const ctx = alertDialogStyleContext(theme);
const { from, to, progress } = useStateTransition(
steps,
"closed",
speed,
defaultDuration,
);
const t = easings.out(progress);
return tweenAlertDialogStyle(
alertDialogStyle(from, ctx),
alertDialogStyle(to, ctx),
t,
);
}