About
Modals are easy to get wrong. The platform element already does focus, Escape and inertness; this adds the words and the colours.
Use it when
- An action cannot be undone.
Skip it when
- The action can be undone: show a toast with Undo.
Install
- 1Add the VibeBox registry to
components.json:{ "registries": { "@vibebox": "https://motion.vibeboxph.com/r/{name}.json" } } - 2Add the component with the shadcn CLI:
npx shadcn@latest add @vibebox/confirm-dialog - 3Use it:
import { ConfirmDialog } from "@/components/vibebox/confirm-dialog"; <ConfirmDialog accent="#7c5cff" />
AI prompt
Everything an AI coding tool needs to rebuild this component the way it looks here: colours, fonts, the art it draws, props and rules.
Read the prompt
Build the VibeBox Motion component "Confirm Dialog" (Overlays) in a Next.js App Router project with Tailwind CSS v4.
What it is
- A confirmation on the native dialog: focus trapped, Escape closes, a clear cancel and a destructive confirm.
- A confirm dialog built on the browser's `dialog` element, so focus is trapped and returned, Escape closes and the page behind is inert without a library. The cancel button has initial focus (the safe choice), and the confirm button is in the destructive colour or the accent.
Install
- `npx shadcn@latest add @vibebox/confirm-dialog`
- Registry: add `"@vibebox": "https://motion.vibeboxph.com/r/{name}.json"` to components.json, then run the command. The source lands in `components/vibebox/confirm-dialog` and is yours to edit.
Visual spec
- Colours (the VibeBox spectrum, the same in light and dark):
- --spectrum-1: #00c5fc (cyan)
- --spectrum-2: #1d6ef0 (blue)
- --spectrum-3: #5225ed (indigo)
- --spectrum-4: #e434dc (magenta)
- --spectrum-5: #fd3aa9 (pink)
- --spectrum-6: #f98472 (coral)
- --spectrum-7: #fcbe72 (amber)
- Ink surface (dark): background #05070c, card #0a0d15, text #f5f6fa, muted text #a3abbd, border rgba(255,255,255,0.08).
- Paper surface (light): background #f3f0ea, card #fbfaf7, text #0c0e14, muted text #4a4f5c, border rgba(12,14,20,0.12).
- Fonts:
- Display and body: Figtree (400, 500, 600, 700), falling back to ui-sans-serif, system-ui.
- Emphasis: Fraunces italic, for one or two words of a heading.
- Labels and code: JetBrains Mono (400, 500), uppercase labels with 0.14em tracking.
- Shape: radius 8 to 12px on cards and panels; pills may be fully round. Hairline borders, soft glow instead of heavy shadow.
- Motion: animate transform and opacity only; fast ease-out (cubic-bezier(0.16, 1, 0.3, 1)), 140ms for micro-interactions, 320 to 520ms for reveals.
Art and media
- Built from HTML, CSS and inline SVG only; no image files or video.
Props
- `trigger`: `string` (default "Cancel booking"). The button that opens the dialog.
- `title`: `string`. The dialog's heading.
- `body`: `string`. What will happen.
- `confirm`: `string`. The confirm button's label.
- `cancel`: `string`. The cancel button's label.
- `danger`: `boolean` (default true). Use the destructive colour for the confirm button; false uses the accent.
- `onConfirm`: `() => void`. Called when confirmed.
- `accent`: `string` (default "blue"). The accent colour: blue, violet, emerald, amber, rose, or any CSS colour. It sets --app-accent on the root, so a parent can set it once for a whole screen.
- `className`: `string`. Merged with cn(); give it a width.
Behaviour and access
- Reduced motion: Nothing animates on its own.
- Keyboard: Tab cycles inside the dialog; Escape closes; the cancel button has initial focus.
- Screen readers: Semantic elements with accessible names; decorative marks are hidden.
- Touch: Targets are 36 px or larger; nothing depends on hover.
- Budget: about 1 KB of JavaScript, WebGL none, GPU none. It pauses while off screen.
Theming
Everything reads `--app-accent` (set by the `accent` prop, or by a parent) and the mode's tokens, so light and dark and any brand colour work without code changes. Pass a named accent or any CSS colour; Launchpad sets it once per app from the client's theme.
Usage
```tsx
import { ConfirmDialog } from "@/components/vibebox/confirm-dialog";
<ConfirmDialog accent="#7c5cff" />
```
Notes
Add the VibeBox Motion Confirm Dialog with `npx shadcn@latest add @vibebox/confirm-dialog`. Call your delete function in `onConfirm`. Set the brand colour once with `--app-accent` on a parent (or the `accent` prop). It follows light and dark through the tokens; keep the licence header.
Rules
- Keep the licence header on every installed file.
- Original code only: do not copy components from other libraries, and do not add GSAP.
- Use tokens for colour, type and radius; no hard-coded brand values.
- Server Components by default; `"use client"` only for the island.Code
2 filescomponents/vibebox/confirm-dialog/confirm-dialog.client.tsx
/**
* VibeBox Motion · Confirm Dialog
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
"use client";
import { useRef } from "react";
import { cn } from "@/lib/utils";
import { accentStyle } from "@/lib/vibebox/app-accent";
export type ConfirmDialogProps = {
trigger?: string;
title?: string;
body?: string;
confirm?: string;
cancel?: string;
danger?: boolean;
onConfirm?: () => void;
accent?: string;
className?: string;
};
/** A confirmation on the native dialog element: focus is trapped, Escape closes, the page behind is inert. */
export function ConfirmDialog({ trigger = "Cancel booking", title = "Cancel this booking?", body = "The client is told by email, and the slot opens up again. This cannot be undone.", confirm = "Cancel booking", cancel = "Keep it", danger = true, onConfirm, accent, className }: ConfirmDialogProps) {
const ref = useRef<HTMLDialogElement>(null);
return (
<div data-vb="confirm-dialog" style={accentStyle(accent)} className={cn("inline-block", className)}>
<button type="button" onClick={() => ref.current?.showModal()} className="h-10 rounded-md border border-border bg-card px-4 text-small font-medium transition-colors hover:bg-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring">
{trigger}
</button>
<dialog
ref={ref}
aria-labelledby="vb-confirm-title"
onClick={(event) => {
if (event.target === ref.current) ref.current?.close();
}}
className="m-auto w-[min(26rem,calc(100vw-2rem))] rounded-xl border border-border bg-popover p-5 text-popover-foreground shadow-md backdrop:bg-black/50 backdrop:backdrop-blur-[2px]"
>
<h2 id="vb-confirm-title" className="text-body font-semibold">{title}</h2>
<p className="mt-1.5 text-small text-muted-foreground">{body}</p>
<div className="mt-5 flex justify-end gap-2">
<button type="button" autoFocus onClick={() => ref.current?.close()} className="h-10 rounded-md border border-border px-4 text-small font-medium hover:bg-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring">{cancel}</button>
<button
type="button"
onClick={() => {
onConfirm?.();
ref.current?.close();
}}
className={cn("h-10 rounded-md px-4 text-small font-semibold transition-opacity hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", danger ? "bg-destructive text-white" : "bg-[var(--app-accent)] text-[var(--app-accent-fg,#fff)]")}
>
{confirm}
</button>
</div>
</dialog>
</div>
);
}components/vibebox/confirm-dialog/index.tsx
/**
* VibeBox Motion · Confirm Dialog
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
export { ConfirmDialog, type ConfirmDialogProps } from "./confirm-dialog.client";Props
| Prop | Type | Default | Description |
|---|---|---|---|
| trigger | string | "Cancel booking" | The button that opens the dialog. |
| title | string | — | The dialog's heading. |
| body | string | — | What will happen. |
| confirm | string | — | The confirm button's label. |
| cancel | string | — | The cancel button's label. |
| danger | boolean | true | Use the destructive colour for the confirm button; false uses the accent. |
| onConfirm | () => void | — | Called when confirmed. |
| accent | string | "blue" | The accent colour: blue, violet, emerald, amber, rose, or any CSS colour. It sets --app-accent on the root, so a parent can set it once for a whole screen. |
| className | string | — | Merged with cn(); give it a width. |
Accessibility and motion
- Keyboard
- Tab cycles inside the dialog; Escape closes; the cancel button has initial focus.
- Screen readers
- Semantic elements with accessible names; decorative marks are hidden.
- Touch
- Targets are 36 px or larger; nothing depends on hover.
- Reduced motion
- Nothing animates on its own.
Edge cases and limits
- Only one dialog title id is used; render one dialog at a time on a page.
Related

Toast Stack
FreeA stack of dismissible notices in a polite live region, with an accent marker.
Overlays · CSS

Glass Dialog
FreeA confirmation dialog on glass over a dimmed scrim, with focus kept inside and Escape to cancel.
Liquid glass · CSS
