Overview
The dialog widget is a simplified API for creating confirmation dialogs with declarative action buttons. It’s syntactic sugar over the modal widget, designed for common confirmation, alert, and prompt flows.
Basic Usage
import { ui } from "@rezi-ui/core";
import { defineWidget } from "@rezi-ui/core";
const DialogExample = defineWidget((ctx) => {
const [showDialog, setShowDialog] = ctx.useState(false);
return ui.layers([
ui.column({ gap: 1, p: 1 }, [
ui.button({
id: "open-dialog",
label: "Open Dialog",
onPress: () => setShowDialog(true),
}),
]),
showDialog &&
ui.dialog({
id: "confirm-dialog",
title: "Confirm Action",
message: "Are you sure you want to proceed?",
actions: [
{
label: "Cancel",
onPress: () => setShowDialog(false),
},
{
label: "Confirm",
intent: "primary",
onPress: () => {
handleConfirm();
setShowDialog(false);
},
},
],
onClose: () => setShowDialog(false),
}),
]);
});
Props
Unique identifier for focus routing.
Dialog message content. Can be plain text or any VNode.
actions
readonly DialogAction[]
required
Array of action button descriptors.
Optional title rendered in dialog header.
Callback when dialog should close.
Modal Props
Dialog accepts all modal props except content and actions:
backdrop
ModalBackdrop
default:"'dim'"
Backdrop style: "none", "dim", "opaque", or custom config.
Close when backdrop is clicked.
Close when ESC key is pressed.
ID of element to focus when dialog opens.
ID of element to return focus to when dialog closes.
DialogAction Interface
type DialogAction = {
id?: string;
label: string;
intent?: "primary" | "danger";
onPress: () => void;
disabled?: boolean;
focusable?: boolean;
};
Callback when button is pressed.
Button intent:
"primary" - Primary call-to-action
"danger" - Destructive action
Optional button ID. Auto-generated if omitted.
Disable the action button.
Control focus order participation.
Keyboard Controls
| Key | Action |
|---|
ESC | Close dialog |
Tab | Navigate between action buttons |
Enter | Activate focused button |
Confirmation Dialog
import { ui } from "@rezi-ui/core";
ui.dialog({
id: "confirm",
title: "Confirm Action",
message: "Are you sure you want to continue?",
actions: [
{
label: "Cancel",
onPress: () => closeDialog(),
},
{
label: "Continue",
intent: "primary",
onPress: () => {
performAction();
closeDialog();
},
},
],
onClose: closeDialog,
});
Destructive Confirmation
import { ui } from "@rezi-ui/core";
ui.dialog({
id: "delete-confirm",
title: "Delete Item",
message: "This action cannot be undone. Are you sure?",
actions: [
{
label: "Cancel",
onPress: () => closeDialog(),
},
{
label: "Delete",
intent: "danger",
onPress: () => {
deleteItem();
closeDialog();
},
},
],
backdrop: "opaque",
closeOnBackdrop: false, // Force explicit choice
closeOnEscape: false,
onClose: closeDialog,
});
Alert Dialog
import { ui } from "@rezi-ui/core";
ui.dialog({
id: "alert",
title: "Operation Complete",
message: "Your changes have been saved successfully.",
actions: [
{
label: "OK",
intent: "primary",
onPress: () => closeDialog(),
},
],
onClose: closeDialog,
});
Multi-Action Dialog
import { ui } from "@rezi-ui/core";
ui.dialog({
id: "unsaved-changes",
title: "Unsaved Changes",
message: "You have unsaved changes. What would you like to do?",
actions: [
{
label: "Discard",
intent: "danger",
onPress: () => {
discardChanges();
closeDialog();
},
},
{
label: "Cancel",
onPress: () => closeDialog(),
},
{
label: "Save",
intent: "primary",
onPress: () => {
saveChanges();
closeDialog();
},
},
],
closeOnBackdrop: false,
onClose: closeDialog,
});
Rich Content Dialog
Use VNode for complex message content:
import { ui } from "@rezi-ui/core";
ui.dialog({
id: "rich-dialog",
title: "Delete Multiple Items",
message: ui.column({ gap: 1 }, [
ui.text("The following items will be deleted:"),
ui.column({ gap: 0, pl: 2 }, [
ui.text("• Document.pdf"),
ui.text("• Image.png"),
ui.text("• Video.mp4"),
]),
ui.text("This action cannot be undone.", { style: { bold: true } }),
]),
actions: [
{
label: "Cancel",
onPress: closeDialog,
},
{
label: "Delete All",
intent: "danger",
onPress: () => {
deleteItems();
closeDialog();
},
},
],
onClose: closeDialog,
});
Disabled Actions
import { defineWidget } from "@rezi-ui/core";
const ValidatedDialog = defineWidget((ctx) => {
const [agreed, setAgreed] = ctx.useState(false);
return ui.dialog({
id: "terms-dialog",
title: "Terms and Conditions",
message: ui.column({ gap: 1 }, [
ui.text("Please read and accept the terms and conditions."),
ui.checkbox({
id: "terms-checkbox",
checked: agreed,
label: "I agree to the terms and conditions",
onChange: setAgreed,
}),
]),
actions: [
{
label: "Cancel",
onPress: closeDialog,
},
{
label: "Accept",
intent: "primary",
disabled: !agreed,
onPress: () => {
acceptTerms();
closeDialog();
},
},
],
closeOnBackdrop: false,
closeOnEscape: false,
onClose: closeDialog,
});
});
Error Dialog
import { ui } from "@rezi-ui/core";
ui.dialog({
id: "error-dialog",
title: "Error",
message: ui.column({ gap: 1 }, [
ui.text("An error occurred:", { style: { bold: true } }),
ui.text(error.message, { style: { fg: { r: 255, g: 100, b: 100 } } }),
error.stack && ui.text(error.stack, { variant: "code", dim: true }),
]),
actions: [
{
label: "Copy Error",
onPress: () => {
copyToClipboard(error.message);
},
},
{
label: "Close",
intent: "primary",
onPress: closeDialog,
},
],
width: 60,
onClose: closeDialog,
});
Loading Dialog
import { ui } from "@rezi-ui/core";
ui.dialog({
id: "loading-dialog",
title: "Processing",
message: ui.column({ gap: 1, items: "center" }, [
ui.spinner({ variant: "dots" }),
ui.text("Please wait..."),
]),
actions: [], // No actions during loading
closeOnBackdrop: false,
closeOnEscape: false,
});
Dialog vs Modal
Use Dialog when:
- Simple confirmation or alert
- 1-3 action buttons
- Plain text or simple message content
Use Modal when:
- Complex forms or multi-step flows
- Custom layout control needed
- Dynamic action buttons (not declarative array)
// Dialog: Simple and declarative
ui.dialog({
id: "simple",
message: "Confirm?",
actions: [
{ label: "No", onPress: close },
{ label: "Yes", intent: "primary", onPress: confirm },
],
});
// Modal: Complex and flexible
ui.modal({
id: "complex",
title: "Edit Profile",
content: ui.form([...formFields]),
actions: [cancelButton, saveButton],
});
Best Practices
- Keep messages concise - One or two sentences max
- Use intent correctly -
"primary" for safe actions, "danger" for destructive
- Provide escape route - Always include Cancel or Close option
- Focus primary action - Use
initialFocus to direct keyboard users
- Disable backdrop close - For critical decisions, set
closeOnBackdrop: false
- Rich content sparingly - Use VNodes only when necessary
Accessibility
- Focus automatically trapped within dialog
- Tab navigates between action buttons
- ESC closes dialog (unless disabled)
- Action buttons are keyboard accessible
- Screen readers announce dialog title and message
- Modal - For complex dialogs with custom layout
- Callout - For inline alert messages
- Toast - For non-blocking notifications
Location in Source
- Types:
packages/core/src/widgets/types.ts:1090-1109
- Factory:
packages/core/src/widgets/ui.ts:1208