Dialog
General-purpose focus-trapping overlay on the platform <dialog>, with backdrop blur, a slide-up entrance, and Escape-to-dismiss. For forms, settings, and 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 when you need a container for forms, settings panels, content previews, or multi-step workflows where background interaction must be blocked. The native top layer and ::backdrop handle stacking and modality.
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. The reverse also bites: 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 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 come 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 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
- ConfirmProgrammatic confirmation API that wraps dialog. Call ArcConfirm.open() and await the returned promise. Looks the same as dialog.
- SheetA sliding overlay panel that emerges from the bottom or right edge of the viewport, with a blurred backdrop, header, scrollable body, and footer slot.
- DrawerSlide-out panel with backdrop overlay, keyboard dismissal, and left/right positioning for off-canvas navigation, filters, and detail views.