Skip to content

FreeIn reviewv1.0.0

Confirm Dialog

A confirmation on the native dialog: focus trapped, Escape closes, a clear cancel and a destructive confirm.
npx shadcn@latest add @vibebox/confirm-dialog

Controls

<ConfirmDialog />

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

  1. 1Add the VibeBox registry to components.json:
    {
      "registries": {
        "@vibebox": "https://motion.vibeboxph.com/r/{name}.json"
      }
    }
  2. 2Add the component with the shadcn CLI:
    npx shadcn@latest add @vibebox/confirm-dialog
  3. 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 files
components/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

PropTypeDefaultDescription
triggerstring"Cancel booking"The button that opens the dialog.
titlestring—The dialog's heading.
bodystring—What will happen.
confirmstring—The confirm button's label.
cancelstring—The cancel button's label.
dangerbooleantrueUse the destructive colour for the confirm button; false uses the accent.
onConfirm() => void—Called when confirmed.
accentstring"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.
classNamestring—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.
  • Two notice cards with an accent dot, a title, a line and a close button

    A stack of dismissible notices in a polite live region, with an accent marker.

    Overlays · CSS

  • A glass confirmation dialog with two buttons over a dimmed colourful page

    A confirmation dialog on glass over a dimmed scrim, with focus kept inside and Escape to cancel.

    Liquid glass · CSS

  • A list of people with accent monograms, short activity sentences and times

    A list of who did what and when, with initials in the accent.

    Data · Server

↑ ↓ to move↵ to openesc to close