Dialog
General-purpose focus-trapping overlay on the platform <dialog> — backdrop blur, slide-up entrance, and Escape-to-dismiss for forms, settings, and rich content that needs full user attention.
<arc-dialog> Overview
Dialog is the general-purpose overlay primitive, built on the platform's native <dialog> element. It floats above the page behind a blurred backdrop, moves focus inside on open, and returns focus to the trigger element on close. Use it any time you need a rich container for forms, settings panels, content previews, or multi-step workflows where background interaction must be blocked — the native top layer and ::backdrop do the heavy lifting the way the platform intends.
Renamed in v4. This component was arc-modal through v3; the element is a dialog, the platform calls it a dialog, and modal named one of its behaviours rather than what it is. The old tag is gone in v4.0.0 — removed rather than aliased, since v4 never shipped and an alias would have served nobody. Note the hazard in the other direction: the v3 tag arc-dialog was a small confirm prompt, and that component is now arc-confirm. Handing this Dialog the old prompt props (message, confirm-label, cancel-label) logs a console.error naming arc-confirm rather than silently ignoring them.
The component ships with three width presets (sm/md/lg), a fullscreen mode, and a smooth slide-up entrance. Dismissal is governed by one prop: dismissible renders the built-in close button and enables Escape and backdrop click, and it defaults to on — a dialog is dismissible unless you say otherwise. Set it to false for decisions the user must resolve through the footer buttons. The arc-close event is cancelable, so preventDefault() can veto a close while a form inside is mid-save.
Guidelines
When to use
- Use Dialog for rich content that blocks the page: edit forms, creation wizards, detail views, settings panels
- Keep `dismissible` on unless abandoning the dialog would lose meaningful user state
- Put primary actions in the `footer` slot, with the confirming action last
- Use `size="sm"` for short single-purpose dialogs and `lg` only for genuinely dense content
- Cancel the `arc-close` event to hold the dialog open while an in-flight save completes
When not to use
- Do not use Dialog for a yes/no prompt — that is `arc-confirm`, which exists so you never rebuild the two-button layout
- Do not stack dialogs — resolve one before opening another
- Do not use Dialog for passive notifications — use Alert or Toast, which do not steal focus
- Do not disable `dismissible` for convenience; an inescapable overlay must be earning that severity
Features
- Built on the native `<dialog>` element — top layer, `::backdrop`, and modality from the platform
- Automatic focus trap — focus moves inside on open and returns to the trigger on close
- Backdrop blur and dim, styled via `--dialog-backdrop` and `--dialog-backdrop-filter`
- Slide-up entry and fade-out exit animations
- `dismissible` (default on): built-in close button, Escape key, and backdrop click
- Cancelable `arc-close` event — `preventDefault()` vetoes the close
- Three width presets: sm (400px), md (560px), lg (720px), plus `fullscreen`
- `header` and `footer` slots around arbitrary body content
- `heading` doubles as the dialog’s accessible name
Preview
Usage
This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.
<arc-button id="edit">Edit Profile</arc-button>
<arc-dialog heading="Edit Profile" size="sm">
<arc-input label="Display Name"></arc-input>
<div slot="footer">
<arc-button variant="ghost" data-close>Cancel</arc-button>
<arc-button variant="primary" data-close>Save Changes</arc-button>
</div>
</arc-dialog>
<script>
const dialog = document.querySelector('arc-dialog');
document.getElementById('edit').addEventListener('click', () => {
dialog.open = true;
});
dialog.addEventListener('click', (e) => {
if (e.target.closest('[data-close]')) dialog.open = false;
});
</script> import { Dialog, Button, Input } from '@arclux/arc-ui-react';
import { useState } from 'react';
function EditProfile() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Edit Profile</Button>
<Dialog heading="Edit Profile" size="sm" open={open} onArcClose={() => setOpen(false)}>
<Input label="Display Name" />
<div slot="footer">
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="primary" onClick={() => setOpen(false)}>Save Changes</Button>
</div>
</Dialog>
</>
);
} <script setup>
import { ref } from 'vue';
import { Dialog, Button, Input } from '@arclux/arc-ui-vue';
const open = ref(false);
</script>
<template>
<Button @click="open = true">Edit Profile</Button>
<Dialog heading="Edit Profile" size="sm" :open="open" @arc-close="open = false">
<Input label="Display Name" />
<div slot="footer">
<Button variant="ghost" @click="open = false">Cancel</Button>
<Button variant="primary" @click="open = false">Save Changes</Button>
</div>
</Dialog>
</template> <script>
import { Dialog, Button, Input } from '@arclux/arc-ui-svelte';
let open = false;
</script>
<Button on:click={() => (open = true)}>Edit Profile</Button>
<Dialog heading="Edit Profile" size="sm" {open} on:arc-close={() => (open = false)}>
<Input label="Display Name" />
<div slot="footer">
<Button variant="ghost" on:click={() => (open = false)}>Cancel</Button>
<Button variant="primary" on:click={() => (open = false)}>Save Changes</Button>
</div>
</Dialog> import { Component } from '@angular/core';
import { Dialog, Button, Input } from '@arclux/arc-ui-angular';
@Component({
imports: [Dialog, Button, Input],
template: `
<arc-button (click)="open = true">Edit Profile</arc-button>
<arc-dialog heading="Edit Profile" size="sm" [open]="open" (arc-close)="open = false">
<arc-input label="Display Name" />
<div slot="footer">
<arc-button variant="ghost" (click)="open = false">Cancel</arc-button>
<arc-button variant="primary" (click)="open = false">Save Changes</arc-button>
</div>
</arc-dialog>
`,
})
export class EditProfileComponent {
open = false;
} import { Dialog, Button, Input } from '@arclux/arc-ui-solid';
import { createSignal } from 'solid-js';
function EditProfile() {
const [open, setOpen] = createSignal(false);
return (
<>
<Button onClick={() => setOpen(true)}>Edit Profile</Button>
<Dialog heading="Edit Profile" size="sm" open={open()} on:arc-close={() => setOpen(false)}>
<Input label="Display Name" />
<div slot="footer">
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="primary" onClick={() => setOpen(false)}>Save Changes</Button>
</div>
</Dialog>
</>
);
} import { Dialog, Button, Input } from '@arclux/arc-ui-preact';
import { useState } from 'preact/hooks';
function EditProfile() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Edit Profile</Button>
<Dialog heading="Edit Profile" size="sm" open={open} onArcClose={() => setOpen(false)}>
<Input label="Display Name" />
<div slot="footer">
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="primary" onClick={() => setOpen(false)}>Save Changes</Button>
</div>
</Dialog>
</>
);
} API
-
REUSED_TAG_PROPSarray['message', 'confirmLabel', 'cancelLabel'] - The props that belonged to the *old*
arc-dialog. V4-SCOPE §3.3 requires this and says why: the oldarc-dialogwas a confirm prompt withheading,messageandconfirmLabel, and it merged intoarc-confirm. Anyone still writing that markup upgrades into this primitive, which knowsheadingand would silently ignore the other two — rendering an empty panel with a title. That is the quietest possible failure, and a line in MIGRATION.md is not a fix for it.headingis deliberately not in the list: it means the same thing in both components, so its presence is not evidence of the mistake. -
headingstring'' - Text displayed in the header bar, and the dialog's accessible name. Keep it short and action-oriented (e.g. "Delete Project" rather than "Are you sure?").
-
openbooleanfalse - Controls the visible state of the dialog. Set to
trueto open it and move focus inside; set tofalseto run the exit animation and restore focus to wherever it came from. -
size'sm' | 'md' | 'lg''md' - Controls the maximum width of the dialog panel.
sm(400px) is ideal for simple confirmations,md(560px) for standard forms, andlg(720px) for content-heavy dialogs with tables or multi-column layouts. -
dismissiblebooleantrue - When
true, renders the built-in X close button and allows dismissal via Escape key and backdrop click. Set tofalsefor critical decisions the user must resolve through the footer buttons. Note the default: a dialog is dismissible unless you say otherwise, where an alert is not dismissible unless you say so — the name is the convention, the default belongs to the component. -
fullscreenbooleanfalse - Makes the dialog fill the entire viewport. Useful for mobile forms or complex workflows.
Events
-
arc-close - Fired when the dialog closes. Cancelable:
preventDefault()vetoes the close. -
arc-open - Fired when the dialog opens
See Also
- Confirm Programmatic confirmation API that wraps dialog. Call ArcConfirm.open() and await the returned promise. Same visual treatment as dialog.
- Sheet A sliding overlay panel that emerges from the bottom or right edge of the viewport, with a blurred backdrop, header, scrollable body, and footer slot.
- Drawer Slide-out panel with backdrop overlay, keyboard dismissal, and left/right positioning for off-canvas navigation, filters, and detail views.