About
A confirm step that matches a glass interface. It does not manage open state or inert the page: render it when you need it and place it in a relative or fixed parent.
Use it when
- Destructive or irreversible confirmations.
Skip it when
- Long forms: use a page or a sheet.
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/glass-dialog
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 "Glass Dialog" (Liquid glass) in a Next.js App Router project with Tailwind CSS v4.
What it is
- A confirmation dialog on glass over a dimmed scrim, with focus kept inside and Escape to cancel.
- An alert dialog on liquid glass: focus lands on the safe action, Tab stays inside, Escape cancels, and a scrim dims the page.
Install
- `npx shadcn@latest add @vibebox/glass-dialog`
- Registry: add `"@vibebox": "https://motion.vibeboxph.com/r/{name}.json"` to components.json, then run the command. The source lands in `components/vibebox/glass-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
- `title`: `string`. The question.
- `children`: `ReactNode`. The explanation.
- `confirmLabel`: `string` (default "Delete"). Confirm button text.
- `cancelLabel`: `string` (default "Keep it"). Cancel button text.
- `danger`: `boolean` (default true). Red confirm button.
- `onConfirm`: `() => void`. Confirm handler.
- `onCancel`: `() => void`. Cancel and Escape handler.
- `scrim`: `boolean` (default true). Fill the positioned parent with a dim scrim and centre the dialog.
- `tone`: `"auto" | "dark" | "light"` (default "auto"). Tints the glass and sets the text colour for the page behind it.
- `className`: `string`. Merged with cn(); sets the width and margins.
Behaviour and access
- Reduced motion: The pointer sheen and sliding are off; state changes are instant.
- Keyboard: Focus starts on the cancel button; Tab and Shift+Tab stay inside; Escape cancels.
- Screen readers: role=alertdialog, aria-modal, labelled by its title.
- Touch: Buttons are 36 px tall.
- Budget: about 3 KB of JavaScript, WebGL none, GPU low. It pauses while off screen.
Theming
The surface reads `--g-tint`, `--g-edge`, `--g-ink`, `--g-blur` and `--g-radius` from its root; the demo backdrop uses `--spectrum-*`.
Notes
Add the VibeBox Motion Glass Dialog with `npx shadcn@latest add @vibebox/glass-dialog`. Render it only when open, inside a fixed full-screen wrapper, with a `title`, an explanation, and `onConfirm` and `onCancel`. Keep focus on the safe action, the focus trap and Escape handling.
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/glass-dialog/glass-dialog.client.tsx
/**
* VibeBox Motion · Glass 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 { useEffect, useRef, type KeyboardEvent, type ReactNode } from "react";
import { cn } from "@/lib/utils";
import { Glass } from "@/lib/vibebox/glass";
export type GlassDialogProps = {
title?: string;
children?: ReactNode;
confirmLabel?: string;
cancelLabel?: string;
/** Marks the confirm action as destructive (a red face). */
danger?: boolean;
onConfirm?: () => void;
onCancel?: () => void;
/** Draw a dimming scrim that fills the nearest positioned parent (put the dialog in a `relative` box, or `fixed` for the page). */
scrim?: boolean;
tone?: "auto" | "dark" | "light";
className?: string;
};
/**
* A confirmation dialog on glass. Focus lands on the safe action, Escape cancels, and Tab stays
* inside it. With `scrim` it fills its positioned parent with a dim backdrop and centres itself.
*/
export function GlassDialog({
title = "Delete this project?",
children = "This removes the project and its preview. You can restore it from the archive for 30 days.",
confirmLabel = "Delete",
cancelLabel = "Keep it",
danger = true,
onConfirm,
onCancel,
scrim = true,
tone,
className,
}: GlassDialogProps) {
const root = useRef<HTMLDivElement>(null);
const cancel = useRef<HTMLButtonElement>(null);
useEffect(() => {
cancel.current?.focus({ preventScroll: true });
}, []);
const onKey = (event: KeyboardEvent) => {
if (event.key === "Escape") {
onCancel?.();
return;
}
if (event.key !== "Tab" || !root.current) return;
const stops = [...root.current.querySelectorAll<HTMLElement>("button")];
const first = stops[0];
const last = stops[stops.length - 1];
if (event.shiftKey && document.activeElement === first) {
last?.focus();
event.preventDefault();
} else if (!event.shiftKey && document.activeElement === last) {
first?.focus();
event.preventDefault();
}
};
const dialog = (
<Glass
as="div"
role="alertdialog"
aria-modal="true"
aria-labelledby="vb-gd-title"
data-vb="glass-dialog"
tone={tone}
blur={22}
onKeyDown={onKey}
className={cn("w-full max-w-[22rem] p-5", className)}
>
<div ref={root}>
<h2 id="vb-gd-title" className="text-[1.05rem] leading-snug font-semibold tracking-tight">
{title}
</h2>
<p className="mt-1.5 text-[0.88rem] leading-relaxed opacity-80">{children}</p>
<div className="mt-5 flex justify-end gap-2">
<button
ref={cancel}
type="button"
onClick={onCancel}
className="h-9 rounded-lg border border-white/30 bg-white/15 px-3.5 text-[0.86rem] font-medium transition-colors hover:bg-white/25 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
>
{cancelLabel}
</button>
<button
type="button"
onClick={onConfirm}
className={cn(
"h-9 rounded-lg px-3.5 text-[0.86rem] font-semibold transition-[filter] hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
danger ? "bg-[#ff4d6d] text-white" : "bg-white text-[#101521]",
)}
>
{confirmLabel}
</button>
</div>
</div>
</Glass>
);
if (!scrim) return dialog;
return <div className="absolute inset-0 z-20 grid place-items-center bg-black/30 p-4 backdrop-blur-[2px]">{dialog}</div>;
}components/vibebox/glass-dialog/index.tsx
/**
* VibeBox Motion · Glass 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 * from "./glass-dialog.client";Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | The question. | |
| children | ReactNode | The explanation. | |
| confirmLabel | string | "Delete" | Confirm button text. |
| cancelLabel | string | "Keep it" | Cancel button text. |
| danger | boolean | true | Red confirm button. |
| onConfirm | () => void | Confirm handler. | |
| onCancel | () => void | Cancel and Escape handler. | |
| scrim | boolean | true | Fill the positioned parent with a dim scrim and centre the dialog. |
| tone | "auto" | "dark" | "light" | "auto" | Tints the glass and sets the text colour for the page behind it. |
| className | string | — | Merged with cn(); sets the width and margins. |
Accessibility and motion
- Keyboard
- Focus starts on the cancel button; Tab and Shift+Tab stay inside; Escape cancels.
- Screen readers
- role=alertdialog, aria-modal, labelled by its title.
- Touch
- Buttons are 36 px tall.
- Reduced motion
- The pointer sheen and sliding are off; state changes are instant.
Related

Glass Controls
FreeA control-centre panel: glass toggle tiles and sliders that are real range inputs.
Liquid glass · CSS

Glass Dock
FreeA floating dock of glass icons that swell toward the pointer, with labels and badges.
Liquid glass · CSS

Glass Nav
FreeA floating pill header on glass with a highlight that glides between links.
Liquid glass · CSS