Dialog
import "@taprootio/espalier/dialog";
<esp-dialog>
Shows content in a modal dialog. By default, on large screens the dialog content is centered horizontally near the top of the screen. On small screens, the content always tries to take up the full screen width.
A field fills one layer above the surface it sits on.
Use surface-layer="1|2|3|4" to select the default painted layer.
When opened, the dialog:
- Moves focus to the first
[autofocus]element inside the slotted content, then the first focusable element (or the dialog container itself if none is found). - Traps focus so that
TabandShift+Tabcycle only through elements inside the dialog. - Closes when the user presses
Escape. - Marks all sibling content as
inertto prevent screen reader and keyboard access to the page behind the overlay. - Restores focus to the element that was focused before the dialog was opened.
Bounded dialog
A dialog covers the whole viewport by default. Set the four inset custom
properties on the esp-dialog host to confine it to a rectangle and leave
the rest of the page visible, for example to keep a header or a live preview
on screen while a full-screen editor is open. The vellum (the dialog's
scrolling surface) and its backdrop paint only inside the rectangle, and a
slotted esp-box full-screen fills exactly that rectangle.
The insets are lengths (0px, 4rem, var(--header-height)), not unitless
numbers, and they are measured from the viewport edges. They are logical, so
"inline start" is the left edge in left-to-right text and the right edge in
right-to-left text. Because custom properties inherit, a dialog nested inside
a bounded dialog's markup is bounded the same way unless it sets its own
insets.
Bounds change only where the dialog paints. What stays reachable is decided by the same rules as an unbounded dialog:
- The dialog has no click-outside dismissal, bounded or not. It closes on
Escape, anesp-form-dialog-close-requestedevent, ortoggleOpen(), so a click on the uncovered region never counts as an "outside" click. - The dialog marks
inertthe siblings of its anchor element, not the regions its bounds leave uncovered. The anchor isesp-page's dialog drop zone when the dialog sits in the page's markup, so there the page's internal tracks (header, menus, preview, help, footer) are inert and not clickable even where the dialog does not cover them. A dialog that is a direct child of another component's shadow root is anchored on the nearest ancestor host that has a light-DOM parent, and only that host's siblings go inert; the page around it stays live. A dialog wrapped in an element inside that shadow root is anchored on itself, so its siblings in the wrapper go inert instead. - A region that is not inert receives clicks and keyboard focus. The focus
trap wraps
TabandShift+Tabat the dialog's first and last focusable elements, andEscapeis handled on the dialog, both only while focus is inside it. A click on an uncovered control moves focus there, and from then onTabcontinues through the page andEscapeno longer closes the dialog. Setinerton the uncovered regions that must not be reachable while the dialog is open (an inert region is also skipped by screen readers). - Bounds assume a horizontal writing mode: the insets are logical, but the dialog's width and height are physical.
Slots
<esp-dialog> has a slot:
Default
Place content to show modally in the default slot.
Dialogs work well as containers for complex forms. Picker menus, inputs, and other interactive controls render correctly inside the dialog overlay.
Attributes
<esp-dialog> has the following attributes:
surface-layer
surface-layer has a default value of undefined.
Declares the painted surface depth; defaults to layer 3.
full-screen
full-screen has a default value of false.
If true, the dialog will take up the full screen. On small screens, the dialog will always be full screen.
Methods
<esp-dialog> has the following methods:
toggleOpen
Show or hide the dialog content. When opening, focus moves
into the dialog and sibling content is marked inert. When
closing, focus is restored to the element that was focused
before the dialog opened.
Dispatches lifecycle events:
esp-dialog-openedafter the dialog is fully rendered and focus has moved in.esp-dialog-closing(cancelable) before the close begins.esp-dialog-closedafter the close completes.
Events
<esp-dialog> emits the following events:
esp-dialog-opened
esp-dialog-opened is of type CustomEvent<{}>.
Fired after the dialog has been promoted to its final DOM position, rendered, and focus has moved in. Safe to initialize third-party widgets (e.g. Stripe Elements) or measure layout at this point.
esp-dialog-closing
esp-dialog-closing is of type CustomEvent<{ reason: "escape" | "close-dialog" | "api" }>.
Fired before the dialog begins closing. Call event.preventDefault() to cancel the close (e.g. for unsaved-changes confirmation). The reason indicates what triggered the close: "escape" for the Escape key, "close-dialog" for a esp-form-dialog-close-requested event from slotted content (typically <esp-form method="dialog">), or "api" for a direct toggleOpen() call.
esp-dialog-closed
esp-dialog-closed is of type CustomEvent<{ reason: "escape" | "close-dialog" | "api" }>.
Fired after the dialog has fully closed, inert state has been restored, and focus has returned to the previously focused element. Safe to tear down resources.
CSS Properties
<esp-dialog> has the following CSS properties:
--esp-color-dialog-bg
The background color of the dialog overlay.
--esp-dialog-bg-opacity
The opacity of the dialog overlay background.
--esp-dialog-inset-block-start
The top edge of the area the dialog covers, as a length from the viewport top. Defaults to 0px.
--esp-dialog-inset-inline-start
The leading edge (left in left-to-right text) of the area the dialog covers, as a length from the viewport edge. Defaults to 0px.
--esp-dialog-inset-inline-end
The trailing edge (right in left-to-right text) of the area the dialog covers, as a length from the viewport edge. Defaults to 0px.
--esp-dialog-inset-block-end
The bottom edge of the area the dialog covers, as a length from the viewport bottom. Defaults to 0px.