alert()
One message, one button. Resolves when it closes.
await alert('This is an alert dialog.', { title: 'Heads up' });
Every surface Forge Dialog ships, running in this page against the same build the rest of the site uses. Each card shows the code that produced what you just saw, so nothing here is a mock-up.
The four entry points that replace the browser built-ins, plus the generic
open() they are all built on.
One message, one button. Resolves when it closes.
await alert('This is an alert dialog.', { title: 'Heads up' });
Resolves true or false rather than blocking the page.
const ok = await confirm('Are you sure you want to continue?', {
title: 'Please confirm',
});
Resolves the typed string, or null when dismissed.
const name = await prompt('What is your name?', {
title: 'Introduce yourself',
defaultValue: 'Ada Lovelace',
});
validate returns true or an error string, and the dialog
stays open until it passes.
await prompt('Enter an even number:', {
title: 'Validated prompt',
validate: (value) => {
const n = Number(value);
if (Number.isNaN(n)) return 'Please enter a number';
if (n % 2 !== 0) return 'Number must be even';
return true;
},
});
Build the body yourself and decide what each button resolves with.
const instance = open({
title: 'Custom dialog',
content: (container) => {
const p = document.createElement('p');
p.textContent = 'Built with the generic open() API.';
container.append(p);
},
buttons: [
{ text: 'Close', role: 'primary', autoFocus: true, onClick: (i) => i.close('done') },
],
});
const result = await instance.whenClosed();
Open a second dialog over the first. Focus, Escape, and scroll locking follow the top of the stack.
const first = confirm('First dialog. Open a second one on top?', { title: 'Dialog 1' });
const second = await confirm('This is stacked above the first dialog.', { title: 'Dialog 2' });
const firstResult = await first;
Set per dialog. Every animation honours prefers-reduced-motion.
Opens and closes instantly.
await alert('This alert opens and closes instantly.', { animation: 'none' });
Slides in from below instead of scaling.
await confirm('This dialog slides in from below.', { animation: 'slide' });
The same core, presented differently. All of these live behind
forgedialog/presentation.
A side panel with the same focus handling as a modal.
drawer({
title: 'Project settings',
message: 'A native, focus-safe drawer.',
side: 'right',
buttons: [{ text: 'Done', role: 'primary', closesDialog: true }],
});
The mobile shape. Drag it down to dismiss.
bottomSheet({
title: 'Quick actions',
message: 'Drag down to dismiss this sheet.',
buttons: [{ text: 'Done', role: 'primary', closesDialog: true }],
});
Announced politely, dismissed on its own. Four tones.
toast('Changes saved successfully.', { tone: 'success' });
An image at full size, with a caption and the same dismissal rules.
lightbox('/og-image.png', {
alt: 'The Forge Dialog social card',
caption: 'Rendered from site/icon.svg at build time.',
});
A blocking state you control. update() changes the message,
close() ends it.
const task = loading('Uploading…');
task.update('Processing…');
await task.close();
Filterable commands with keyboard navigation. Type to narrow.
commandPalette([
{ id: 'theme-dark', label: 'Switch to dark theme', keywords: ['appearance'],
run: () => setTheme('dark') },
{ id: 'theme-light', label: 'Switch to light theme', keywords: ['appearance'],
run: () => setTheme('light') },
]);
Every toast raised in this page, kept in history. Raise a few toasts first, then open it.
notificationCenter();
getNotificationHistory(); // [{ id, message, tone, createdAt }]
clearNotificationHistory();
Behind forgedialog/workflows. In TypeScript the field names are inferred
into the result type, so the values come back typed without a cast.
A validated form from a field list. Resolves the values, or null.
const values = await form(
[
{ name: 'email', type: 'email', label: 'Email', required: true },
{ name: 'plan', type: 'select', label: 'Plan', options: [
{ value: 'free', label: 'Free' },
{ value: 'pro', label: 'Pro' },
] },
{ name: 'notes', type: 'textarea', label: 'Notes', rows: 3 },
],
{ title: 'Create an account' },
);
Multi-step, with per-step validation and shared data.
const flow = wizard({
initialData: { name: '' },
steps: [
{
id: 'profile',
title: 'Your profile',
render: (el, ctx) => {
const input = document.createElement('input');
input.className = 'fd-input';
input.placeholder = 'Name';
input.addEventListener('input', () => ctx.set({ name: input.value }));
el.append(input);
},
validate: (ctx) => (ctx.data.name ? true : 'Please enter your name.'),
},
{ id: 'review', title: 'Review',
render: (el, ctx) => { el.textContent = `Ready to welcome ${ctx.data.name}.`; } },
],
});
const data = await flow.result;
The same field list as form(), spread over steps.
const values = await formWizard([
{ id: 'account', title: 'Account', fields: [
{ name: 'email', type: 'email', label: 'Email', required: true },
] },
{ id: 'profile', title: 'Profile', fields: [
{ name: 'name', type: 'text', label: 'Full name', required: true },
] },
]);
setTheme() drives the library and this page together;
setThemePreset() swaps the whole look.
Light, dark, or follow the operating system.
setTheme('dark'); // 'light' | 'dark' | 'system'
Pick one, then open any demo above to see it applied.
setThemePreset('glass');
Per-dialog colour, radius, shadow direction, hover, and drag bounds have their own interactive builder on the landing page.
open({
title: 'Styled dialog',
appearance: { radius: 20, shadow: { angle: 180, distance: 20, blur: 60 } },
draggable: { bounds: 'viewport' },
});
Every demo above prints its result here, along with the plugin lifecycle hooks it fired
— afterOpen and afterClose from a plugin registered on this
page.