Dialog
<gui-dialog> shows content the user must deal with before going back to the page, such as a
short form or a confirmation. The dialog is your own <dialog> element, so the browser does the
hard parts: it shows it above everything, makes the rest of the page inert, closes it on
Escape and returns the focus to what opened it. The element opens it as a modal,
places it in the middle of the viewport or along one edge, locks the page’s scroll while it is
open, and fires an event when it opens and closes.
The board is called Q3 roadmap.
import { useState, type FormEvent } from 'react';import { GuiButton, GuiDialog, GuiTextinput,} from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DialogBasic() { const [name, setName] = useState('Q3 roadmap');
// The form closes the dialog. Save renames the board, Cancel leaves it. const onSubmit = (event: FormEvent<HTMLFormElement>) => { const submitter = (event.nativeEvent as SubmitEvent).submitter; if ((submitter as HTMLButtonElement | null)?.value !== 'save') return; setName(String(new FormData(event.currentTarget).get('board-name'))); };
return ( <> <GuiButton label="Rename board" variant="outlined" commandFor="rename-board" command="show-modal" /> <GuiDialog> <dialog id="rename-board" aria-labelledby="rename-board-title"> <form method="dialog" className="gui-grid" onSubmit={onSubmit}> <h2 id="rename-board-title">Rename board</h2> <GuiTextinput label="Board name" name="board-name" value="Q3 roadmap" /> <div className="gui-grid gui-grid--row gui-grid--justify-start"> <div className="gui-grid__cell"> <GuiButton type="submit" label="Save" value="save" /> </div> <div className="gui-grid__cell"> <GuiButton type="submit" label="Cancel" value="cancel" variant="outlined" /> </div> </div> </form> </dialog> </GuiDialog> <p id="board-name-status" role="status"> The board is called {name}. </p> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import '@golemui/gui-components/button';import '@golemui/gui-components/dialog';import '@golemui/gui-components/textinput';
@Component({ selector: 'app-dialog-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-button label="Rename board" variant="outlined" commandfor="rename-board" command="show-modal" ></gui-button> <gui-dialog> <dialog id="rename-board" aria-labelledby="rename-board-title"> <form method="dialog" class="gui-grid" (submit)="onSubmit($event)"> <h2 id="rename-board-title">Rename board</h2> <gui-textinput label="Board name" name="board-name" value="Q3 roadmap" ></gui-textinput> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Save" value="save"></gui-button> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Cancel" value="cancel" variant="outlined" ></gui-button> </div> </div> </form> </dialog> </gui-dialog> <p id="board-name-status" role="status">The board is called {{ name() }}.</p> `,})export class DialogBasicComponent { name = signal('Q3 roadmap');
// The form closes the dialog. Save renames the board, Cancel leaves it. onSubmit(event: Event) { const submitter = (event as SubmitEvent).submitter as HTMLButtonElement | null; if (submitter?.value !== 'save') return; const form = event.currentTarget as HTMLFormElement; this.name.set(String(new FormData(form).get('board-name'))); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/button';import '@golemui/gui-components/dialog';import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';
const name = ref('Q3 roadmap');
// The form closes the dialog. Save renames the board, Cancel leaves it.function onSubmit(event: SubmitEvent) { const submitter = event.submitter as HTMLButtonElement | null; if (submitter?.value !== 'save') return; const form = event.currentTarget as HTMLFormElement; name.value = String(new FormData(form).get('board-name'));}</script>
<template> <gui-button label="Rename board" variant="outlined" commandfor="rename-board" command="show-modal" ></gui-button> <gui-dialog> <dialog id="rename-board" aria-labelledby="rename-board-title"> <form method="dialog" class="gui-grid" @submit="onSubmit"> <h2 id="rename-board-title">Rename board</h2> <gui-textinput label="Board name" name="board-name" value="Q3 roadmap" ></gui-textinput> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Save" value="save"></gui-button> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Cancel" value="cancel" variant="outlined" ></gui-button> </div> </div> </form> </dialog> </gui-dialog> <p id="board-name-status" role="status">The board is called {{ name }}.</p></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import '@golemui/gui-components/button';import '@golemui/gui-components/dialog';import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';
@customElement('dialog-basic')export class DialogBasic extends LitElement { @state() private name = 'Q3 roadmap';
protected override createRenderRoot() { return this; }
// The form closes the dialog. Save renames the board, Cancel leaves it. private onSubmit(event: SubmitEvent) { const submitter = event.submitter as HTMLButtonElement | null; if (submitter?.value !== 'save') return; const form = event.currentTarget as HTMLFormElement; this.name = String(new FormData(form).get('board-name')); }
override render() { return html` <gui-button label="Rename board" variant="outlined" commandfor="rename-board" command="show-modal" ></gui-button> <gui-dialog> <dialog id="rename-board" aria-labelledby="rename-board-title"> <form method="dialog" class="gui-grid" @submit=${this.onSubmit}> <h2 id="rename-board-title">Rename board</h2> <gui-textinput label="Board name" name="board-name" value="Q3 roadmap" ></gui-textinput> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Save" value="save"></gui-button> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Cancel" value="cancel" variant="outlined" ></gui-button> </div> </div> </form> </dialog> </gui-dialog> <p id="board-name-status" role="status">The board is called ${this.name}.</p> `; }}<gui-button label="Rename board" variant="outlined" commandfor="rename-board" command="show-modal"></gui-button><gui-dialog> <dialog id="rename-board" aria-labelledby="rename-board-title"> <form method="dialog" class="gui-grid"> <h2 id="rename-board-title">Rename board</h2> <gui-textinput label="Board name" name="board-name" value="Q3 roadmap"></gui-textinput> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Save" value="save"></gui-button> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Cancel" value="cancel" variant="outlined"></gui-button> </div> </div> </form> </dialog></gui-dialog><p id="board-name-status" role="status">The board is called Q3 roadmap.</p>
<script type="module"> const form = document.querySelector('#rename-board form');
// The form closes the dialog. Save renames the board, Cancel leaves it. form.addEventListener('submit', (event) => { if (event.submitter.value !== 'save') return; const name = new FormData(form).get('board-name'); document.querySelector('#board-name-status').textContent = `The board is called ${name}.`; });</script>The element’s entry point is @golemui/gui-components/dialog, which registers <gui-dialog> and
nothing else. To install the package and load the stylesheet, see
Installation.
The @golemui/gui-components/react components have typed props and an on* prop for each event. React 18 or later.
import { GuiDialog } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DialogExample() { return ( <GuiDialog
> <dialog id="rename-board" aria-labelledby="rename-board-title">…</dialog> </GuiDialog> );}Add CUSTOM_ELEMENTS_SCHEMA to the component that uses the tags, and the stylesheet to styles in angular.json.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/dialog';
@Component({ selector: 'app-dialog-example', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-dialog
> <dialog id="rename-board" aria-labelledby="rename-board-title">…</dialog> </gui-dialog> `,})export class DialogExampleComponent {}"styles": ["node_modules/@golemui/gui-components/lib/styles/index.css"]Tell Vue's compiler that gui-* tags are custom elements, and add the typings once for typed props and @gui-* handlers.
<script setup lang="ts">import '@golemui/gui-components/dialog';import '@golemui/gui-components/index.css';</script>
<template> <gui-dialog
> <dialog id="rename-board" aria-labelledby="rename-board-title">…</dialog> </gui-dialog></template>vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('gui-') } },});import type {} from '@golemui/gui-components/vue';The page's stylesheet styles the gui-* elements, so it only reaches them outside a shadow root. Render the host component into the light DOM too, as below, or load the stylesheet into its shadow root.
import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/dialog';import '@golemui/gui-components/index.css';
@customElement('dialog-example')export class DialogExample extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-dialog
> <dialog id="rename-board" aria-labelledby="rename-board-title">…</dialog> </gui-dialog> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-dialog > <dialog id="rename-board" aria-labelledby="rename-board-title">…</dialog></gui-dialog>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/dialog';import '@golemui/gui-components/index.css';The dialog is the element’s <dialog> child. Name it with aria-labelledby, pointing at its
heading, or with aria-label: the element logs a warning in development when it opens a dialog
with no name.
Open it in one of two ways:
- From a button, with no script. A
gui-buttonor a native<button>withcommandfor, the dialog’s id, andcommand="show-modal"opens it, and one withcommand="close"closes it. In React, they are thecommandForandcommandprops ofGuiButton: React’s types don’t know them on a native<button>yet. - From your code. Set
opentotrueorfalse, as in Confirming an action.
A <form method="dialog"> inside closes the dialog when the user submits it, after the form’s
checks pass, and sets the dialog’s returnValue to the value of the button that submitted it.
gui-button with type="submit" submits it like a native button.
The element adds nothing to your markup but the alertdialog role when you ask for it, so a
server-rendered page shows the same markup before and after the element upgrades. A closed
dialog stays hidden until then.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
open | boolean | – | Whether the dialog is open. Without a value it takes the dialog's own state. |
placement | GuiDialogPlacement | – | center (the default), or a drawer along the start, end, top or bottom edge. It reflects, for the stylesheet: set from a framework as a property, it still places the dialog. |
dismissible | boolean | – | Whether a click on the backdrop closes the dialog. An alert dialog ignores it. |
alert | boolean | – | Whether the dialog is an alert dialog: it gets the alertdialog role. |
dialog read-only, no attribute | HTMLDialogElement | null | – | The app's dialog: the element's <dialog> child. |
Events
Every event bubbles and is composed, so you can listen on any ancestor.
| Event | Description |
|---|---|
gui-toggle | The dialog opened or closed, from an invoker, Escape, a backdrop click or a button that closes it. detail.open is the new state. Setting open fires nothing. |
Examples
Section titled “Examples”Drawer
Section titled “Drawer”placement puts the dialog along one edge of the viewport, as a drawer, as tall as the viewport
(or as wide, at the top or the bottom), sliding in from that edge: start, end, top or
bottom. Start and end follow the reading direction. The default, center, is a dialog in the
middle of the viewport.
dismissible lets a click on the backdrop, outside the dialog, close it. It asks the dialog to
close the way Escape does, so a cancel listener that calls preventDefault() can
keep it open, for example while a form inside has unsaved changes.
import { GuiButton, GuiCheckbox, GuiDialog,} from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DialogDrawer() { return ( <> <GuiButton label="Filters" variant="outlined" commandFor="board-filters" command="show-modal" /> <GuiDialog placement="end" dismissible> <dialog id="board-filters" aria-labelledby="board-filters-title"> <form method="dialog" className="gui-grid"> <h2 id="board-filters-title">Filters</h2> <GuiCheckbox label="Assigned to me" name="mine" /> <GuiCheckbox label="Due this week" name="due" /> <GuiCheckbox label="Show completed" name="completed" /> <div className="gui-grid gui-grid--row gui-grid--justify-start"> <div className="gui-grid__cell"> <GuiButton type="submit" label="Apply filters" /> </div> </div> </form> </dialog> </GuiDialog> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/button';import '@golemui/gui-components/checkbox';import '@golemui/gui-components/dialog';
@Component({ selector: 'app-dialog-drawer', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-button label="Filters" variant="outlined" commandfor="board-filters" command="show-modal" ></gui-button> <gui-dialog placement="end" dismissible> <dialog id="board-filters" aria-labelledby="board-filters-title"> <form method="dialog" class="gui-grid"> <h2 id="board-filters-title">Filters</h2> <gui-checkbox label="Assigned to me" name="mine"></gui-checkbox> <gui-checkbox label="Due this week" name="due"></gui-checkbox> <gui-checkbox label="Show completed" name="completed"></gui-checkbox> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Apply filters"></gui-button> </div> </div> </form> </dialog> </gui-dialog> `,})export class DialogDrawerComponent {}<script setup lang="ts">import '@golemui/gui-components/button';import '@golemui/gui-components/checkbox';import '@golemui/gui-components/dialog';import '@golemui/gui-components/index.css';</script>
<template> <gui-button label="Filters" variant="outlined" commandfor="board-filters" command="show-modal" ></gui-button> <gui-dialog placement="end" :dismissible="true"> <dialog id="board-filters" aria-labelledby="board-filters-title"> <form method="dialog" class="gui-grid"> <h2 id="board-filters-title">Filters</h2> <gui-checkbox label="Assigned to me" name="mine"></gui-checkbox> <gui-checkbox label="Due this week" name="due"></gui-checkbox> <gui-checkbox label="Show completed" name="completed"></gui-checkbox> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Apply filters"></gui-button> </div> </div> </form> </dialog> </gui-dialog></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/button';import '@golemui/gui-components/checkbox';import '@golemui/gui-components/dialog';import '@golemui/gui-components/index.css';
@customElement('dialog-drawer')export class DialogDrawer extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-button label="Filters" variant="outlined" commandfor="board-filters" command="show-modal" ></gui-button> <gui-dialog placement="end" dismissible> <dialog id="board-filters" aria-labelledby="board-filters-title"> <form method="dialog" class="gui-grid"> <h2 id="board-filters-title">Filters</h2> <gui-checkbox label="Assigned to me" name="mine"></gui-checkbox> <gui-checkbox label="Due this week" name="due"></gui-checkbox> <gui-checkbox label="Show completed" name="completed"></gui-checkbox> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Apply filters"></gui-button> </div> </div> </form> </dialog> </gui-dialog> `; }}<gui-button label="Filters" variant="outlined" commandfor="board-filters" command="show-modal"></gui-button><gui-dialog placement="end" dismissible> <dialog id="board-filters" aria-labelledby="board-filters-title"> <form method="dialog" class="gui-grid"> <h2 id="board-filters-title">Filters</h2> <gui-checkbox label="Assigned to me" name="mine"></gui-checkbox> <gui-checkbox label="Due this week" name="due"></gui-checkbox> <gui-checkbox label="Show completed" name="completed"></gui-checkbox> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Apply filters"></gui-button> </div> </div> </form> </dialog></gui-dialog>Confirming an action
Section titled “Confirming an action”alert gives the dialog the alertdialog role, for a dialog that asks the user to confirm or
acknowledge something urgent, such as a deletion. Screen readers announce it as an alert, and a
click on the backdrop never closes it, even with dismissible: the user answers it.
This example opens the dialog from your code, setting open when the button is clicked, and reads
the user’s answer from the button that submitted the form.
import { useState, type FormEvent } from 'react';import { GuiButton, GuiDialog } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DialogConfirm() { const [open, setOpen] = useState(false); const [deleted, setDeleted] = useState(false);
// The form closes the dialog. The button that submitted it says what the user chose. const onSubmit = (event: FormEvent<HTMLFormElement>) => { const submitter = (event.nativeEvent as SubmitEvent).submitter; if ((submitter as HTMLButtonElement | null)?.value === 'delete') setDeleted(true); };
return ( <> <GuiButton id="delete-card" label="Delete card" variant="outlined" onClick={() => setOpen(true)} /> <GuiDialog id="delete-dialog" alert open={open} onGuiToggle={(event) => setOpen(event.detail.open)} > <dialog aria-labelledby="delete-title" aria-describedby="delete-text"> <form method="dialog" className="gui-grid" onSubmit={onSubmit}> <h2 id="delete-title">Delete this card?</h2> <p id="delete-text">Its comments and attachments go with it. You can't undo this.</p> <div className="gui-grid gui-grid--row gui-grid--justify-start"> <div className="gui-grid__cell"> <GuiButton type="submit" label="Cancel" value="cancel" variant="outlined" /> </div> <div className="gui-grid__cell"> <GuiButton type="submit" label="Delete card" value="delete" /> </div> </div> </form> </dialog> </GuiDialog> <p id="delete-status" role="status"> {deleted ? 'The card was deleted.' : ''} </p> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiDialogEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/button';import '@golemui/gui-components/dialog';
type DialogEvent<K extends keyof typeof GuiDialogEvents> = GuiEventMap< typeof GuiDialogEvents>[K];
@Component({ selector: 'app-dialog-confirm', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-button id="delete-card" label="Delete card" variant="outlined" (click)="open.set(true)" ></gui-button> <gui-dialog id="delete-dialog" alert [open]="open()" (gui-toggle)="onToggle($event)" > <dialog aria-labelledby="delete-title" aria-describedby="delete-text"> <form method="dialog" class="gui-grid" (submit)="onSubmit($event)"> <h2 id="delete-title">Delete this card?</h2> <p id="delete-text">Its comments and attachments go with it. You can't undo this.</p> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Cancel" value="cancel" variant="outlined"></gui-button> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Delete card" value="delete"></gui-button> </div> </div> </form> </dialog> </gui-dialog> <p id="delete-status" role="status"> {{ deleted() ? 'The card was deleted.' : '' }} </p> `,})export class DialogConfirmComponent { open = signal(false); deleted = signal(false);
onToggle(event: Event) { this.open.set((event as DialogEvent<'gui-toggle'>).detail.open); }
// The form closes the dialog. The button that submitted it says what the user chose. onSubmit(event: Event) { const submitter = (event as SubmitEvent).submitter as HTMLButtonElement | null; if (submitter?.value === 'delete') this.deleted.set(true); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/button';import '@golemui/gui-components/dialog';import '@golemui/gui-components/index.css';
const open = ref(false);const deleted = ref(false);
// The form closes the dialog. The button that submitted it says what the user chose.function onSubmit(event: SubmitEvent) { const submitter = event.submitter as HTMLButtonElement | null; if (submitter?.value === 'delete') deleted.value = true;}</script>
<template> <gui-button id="delete-card" label="Delete card" variant="outlined" @click="open = true" ></gui-button> <gui-dialog id="delete-dialog" :alert="true" :open="open" @gui-toggle="open = $event.detail.open" > <dialog aria-labelledby="delete-title" aria-describedby="delete-text"> <form method="dialog" class="gui-grid" @submit="onSubmit"> <h2 id="delete-title">Delete this card?</h2> <p id="delete-text">Its comments and attachments go with it. You can't undo this.</p> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Cancel" value="cancel" variant="outlined"></gui-button> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Delete card" value="delete"></gui-button> </div> </div> </form> </dialog> </gui-dialog> <p id="delete-status" role="status"> {{ deleted ? 'The card was deleted.' : '' }} </p></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiDialogEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/button';import '@golemui/gui-components/dialog';import '@golemui/gui-components/index.css';
type DialogEvent<K extends keyof typeof GuiDialogEvents> = GuiEventMap< typeof GuiDialogEvents>[K];
@customElement('dialog-confirm')export class DialogConfirm extends LitElement { @state() private open = false; @state() private deleted = false;
protected override createRenderRoot() { return this; }
private onToggle(event: DialogEvent<'gui-toggle'>) { this.open = event.detail.open; }
// The form closes the dialog. The button that submitted it says what the user chose. private onSubmit(event: SubmitEvent) { const submitter = event.submitter as HTMLButtonElement | null; if (submitter?.value === 'delete') this.deleted = true; }
override render() { return html` <gui-button id="delete-card" label="Delete card" variant="outlined" @click=${() => (this.open = true)} ></gui-button> <gui-dialog id="delete-dialog" alert .open=${this.open} @gui-toggle=${this.onToggle} > <dialog aria-labelledby="delete-title" aria-describedby="delete-text"> <form method="dialog" class="gui-grid" @submit=${this.onSubmit}> <h2 id="delete-title">Delete this card?</h2> <p id="delete-text">Its comments and attachments go with it. You can't undo this.</p> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Cancel" value="cancel" variant="outlined"></gui-button> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Delete card" value="delete"></gui-button> </div> </div> </form> </dialog> </gui-dialog> <p id="delete-status" role="status"> ${this.deleted ? 'The card was deleted.' : ''} </p> `; }}<gui-button id="delete-card" label="Delete card" variant="outlined"></gui-button><gui-dialog id="delete-dialog" alert> <dialog aria-labelledby="delete-title" aria-describedby="delete-text"> <form method="dialog" class="gui-grid"> <h2 id="delete-title">Delete this card?</h2> <p id="delete-text">Its comments and attachments go with it. You can't undo this.</p> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Cancel" value="cancel" variant="outlined"></gui-button> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Delete card" value="delete"></gui-button> </div> </div> </form> </dialog></gui-dialog><p id="delete-status" role="status"></p>
<script type="module"> const dialog = document.querySelector('#delete-dialog');
document.querySelector('#delete-card').addEventListener('click', () => { dialog.open = true; });
// The form closes the dialog. The button that submitted it says what the user chose. dialog.querySelector('form').addEventListener('submit', (event) => { if (event.submitter.value === 'delete') { document.querySelector('#delete-status').textContent = 'The card was deleted.'; } });</script>Reacting to opening and closing
Section titled “Reacting to opening and closing”When the dialog opens or closes, from an invoker button, Escape, a backdrop click or a
form inside, the element fires gui-toggle with the new state in event.detail.open, and open
follows. Setting open yourself fires no gui-toggle. In a framework that holds open in its
state, update that state from gui-toggle, as the examples do: otherwise, after the user closes
the dialog, setting it to true again changes nothing.
Toasts in a dialog
Section titled “Toasts in a dialog”While a dialog is open, the rest of the page is inert, and so would be the
toasts the page shows. While a gui-dialog is open, the
toaster shows its toasts inside it, so the user can still read them and use their buttons. A
<dialog> you open with showModal() yourself, outside a gui-dialog, hides them.
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<gui-dialog> has no box of its own (display: contents). The stylesheet draws your <dialog>
as a card over a dimmed backdrop, and while it is open the element has the open attribute:
<gui-dialog placement="end" open> <dialog id="board-filters" aria-labelledby="board-filters-title" open> <h2 id="board-filters-title">Filters</h2> <!-- Your content. --> </dialog></gui-dialog>placement reflects as an attribute, so you can select a drawer as
gui-dialog[placement='end'] > dialog. A centered dialog is --gui-dialog-size wide, and a
drawer --gui-drawer-size, never more than the viewport. They have a set width rather than the
width of their content, so a form inside has room. Content taller than the viewport scrolls
inside the dialog.
/* A wider dialog for the board settings, and a lighter backdrop everywhere. */gui-dialog.board-settings > dialog { --gui-dialog-size: 44rem;}
:root { --gui-dialog-backdrop: rgb(0 0 0 / 0.25);}While a dialog is open, the page under it doesn’t scroll: the stylesheet sets overflow: hidden
on html.
Tokens
Section titled “Tokens”The dialog reads these design tokens. Set them on :root to change every dialog, or on any
element to change the dialogs inside it:
| Token | What it styles |
|---|---|
--gui-dialog-size | The width of a centered dialog, 32rem by default. |
--gui-drawer-size | The width of a start or end drawer, the height of the others, 24rem by default. |
--gui-dialog-backdrop | The backdrop, rgb(0 0 0 / 0.45) by default. |
--gui-bg-default | The background. |
--gui-border-default | The border. |
--gui-radius-lg | The corner radius of a centered dialog. A drawer has square corners. |
--gui-shadow-lg | The shadow. |
--gui-text-default | The text. |
--gui-space-5 | The padding. |
--gui-space-4 | The space a centered dialog keeps from the edges of the viewport. |
--gui-overlay-duration | How long the dialog takes to fade and scale in, or a drawer to slide in. Reduced motion skips it. |
To restyle beyond the tokens, see Overriding styles; for dark mode and the Clay theme, see Themes and dark mode.
Accessibility
Section titled “Accessibility”- Modal. The dialog is a native modal
<dialog>: the browser makes the rest of the page inert, so the focus and screen readers stay in the dialog until it closes. - Focus. Opening the dialog moves the focus into it, to its first focusable element or the
one with
autofocus. Closing it returns the focus to the element that had it before, such as the button that opened it. - Name. Name the dialog with
aria-labelledbyoraria-label. For an alert dialog, pointaria-describedbyat its message too, so screen readers read it when the dialog opens. - Alert dialogs.
alertgives the dialog thealertdialogrole, unless you set a role yourself. Put the focus on the least destructive answer first, as the example does with Cancel.
| Key | Action |
|---|---|
| Tab | Moves between the dialog’s controls. The page behind is inert. |
| Escape | Closes the dialog, unless a cancel listener prevents it. |
What every element shares is in Accessibility.