Skip to main content
On this pageOverview

Dialog

Overview

A modal dialog backed by the native <dialog> element, opened with show() and a high z-index. The framework manages focus trapping, Escape handling, scroll locking, and backdrop rendering. For non-modal floating content, use Popover instead.

See it in an app

Check out how Dialog is wired up in a real Foldkit app.

Examples

Basic

Open the Dialog from a trigger by dispatching your own Message. Fold Dialog.open and Dialog.close into the parent with Update.foldChildStep; both are no-argument child entry points. Spread the closeButton bundle onto a Cancel button to dismiss it. Spread ...title onto a heading element so the Dialog is labeled for screen readers.

Dialog

Animated

Pass isAnimated: true at init to coordinate animations. The component manages an Animation submodel internally. Apply transition classes using data-closed (e.g. data-[closed]:opacity-0 data-[closed]:scale-95).

Animated dialog

Field

A field inside a dialog can open its own overlay, like a Combobox or DatePicker. The overlay portals its panel into the dialog, not to the document body. The panel renders above the dialog's content, a scrolling dialog panel does not clip it, and it stays interactive while the Dialog makes the rest of the page inert. The overlay needs no anchor configuration. Its wrapper needs position: relative, as it does outside a dialog. The overlay's click-outside backdrop goes directly before that wrapper, and a positioned wrapper keeps the trigger or input above the backdrop.

Combobox overlay in a Dialog

Stacked

Use a separate Dialog Model for each level and open the second from a button in the first. The framework stacks them by z-index, isolates the page around the topmost Dialog, and traps focus there. Escape closes the top Dialog first; its parent becomes interactive again and receives focus.

Stacked dialogs

Styling

Dialog is headless. The toView callback receives attribute bundles for the dialog, backdrop, panel, and closeButton, and the consumer composes the markup. Dialog renders no backdrop of its own, so build your own from the backdrop bundle for full control over its appearance.

When isAnimated is true, enter/leave animations flow through the Animation module. Style with CSS transitions or CSS keyframe animations. Animation advances once every animation on the element has settled.

AttributeCondition
data-openPresent on the dialog when visible.
data-closedPresent during close animation.
data-transitionPresent during any animation phase.
data-enterPresent during the enter animation.
data-leavePresent during the leave animation.

Starting with an Open Dialog

Use Dialog.boot() when a Dialog should be open when the application starts. Pass its result to Update.foldChildInit so the parent incorporates the Dialog Model, maps its Commands to the parent Message type, and handles its Opened OutMessage.

Starting with an open Dialog
return Update.foldChildInit(Dialog.boot({ id: 'confirm' }), {
  toParentModel: dialog => ({ dialog }),
  toParentMessage: toGotDialogMessage,
  foldOutMessage: foldDialogOutMessage,
})

See Folding Update with Update.foldChild for the general child-initialization pattern.

Keyboard Interaction

KeyDescription
EscapeCloses the dialog.
TabCycles focus within the dialog.

Accessibility

Focus and Modal Isolation

When the Dialog opens, Foldkit marks surrounding content inert and aria-hidden="true", and sets aria-modal="true" on the Dialog. Keyboard focus stays inside until it closes, then the original inert and ARIA state is restored before focus returns to the element that opened it. The DevTools overlay remains interactive during development.

Names and Descriptions

The dialog always sets aria-labelledby on the native element. Set hasDescription: true when you render a description to add aria-describedby; leaving it false prevents a dangling reference when no description is present. Spread ...title onto your heading (h.h2([...title], [...])) and ...description onto your description element (h.p([...description], [...])). You never construct either id yourself.

Reuse the Managed IDs

The ids are framework-managed (the -dialog-title, -dialog-description, and -panel suffixes on the configured id). Going through the render info keeps them unique for you. The Dialog.titleId(model) and Dialog.descriptionId(model) helpers return the same ids as plain strings for the cases where you need the id as a value outside toView, such as a Command that calls getElementById or a cross-element reference. As a backstop, the runtime warns on any duplicate id in the rendered tree in development.

API Reference

InitConfig

Configuration object passed to Dialog.init().

NameTypeDefaultDescription
idstring—Unique ID for the dialog instance.
isAnimatedbooleanfalseEnables animation coordination for open/close animations.
focusSelectorstring—CSS selector for the element that receives focus when the dialog opens. A selector-based override of the initialFocus marker, for an element whose id you do not own or a descendant selector. Takes precedence over initialFocus; with neither set, focus falls to the first focusable element.

boot

Dialog.boot(config) accepts the same configuration as Dialog.init(config) and returns Update.ReturnWithOutMessage<Dialog.Model, Dialog.Message, Dialog.OutMessage>. Its Model is open, its Commands contain ShowDialog, and its OutMessage is Opened.

ViewConfig

Configuration object passed to Dialog.view().

NameTypeDefaultDescription
modelDialog.Model—The dialog state from your parent Model.
toParentMessage(childMessage: Dialog.Message) => ParentMessage—Wraps Dialog Messages in your parent Message type for Submodel delegation.
toView(render: RenderInfo) => Html—Callback that receives the dialog, backdrop, panel, and closeButton attribute bundles plus a derived isVisible flag, and returns the composed layout. The consumer MUST render an h.dialog(...) element so the framework can open and close it.
hasDescriptionbooleanfalseWhether the dialog renders a description. Adds aria-describedby to the dialog when true.

RenderInfo

Payload delivered to the toView callback each render.

NameTypeDefaultDescription
dialogReadonlyArray<ChildAttribute>—Spread onto an h.dialog(...) element. Carries the id, ARIA labelling and modal state, open prop, positioning style, lifecycle resource acquisition and cleanup, a handler that suppresses native cancel events so canceling a file picker leaves the dialog open, and a mapping from Dom.showDialog's distinct Escape signal to RequestedClose.
backdropReadonlyArray<ChildAttribute>—Spread onto the backdrop element. Includes the Animation data attributes and the outside-click handler that dispatches RequestedClose (suppressed while a leave animation is in progress).
panelReadonlyArray<ChildAttribute>—Spread onto the panel element. Includes the panel id (${id}-panel) and the Animation data attributes.
titleReadonlyArray<ChildAttribute>—Spread onto your accessible-name heading (h.h2([...title], [...])). Carries the framework-managed id the dialog’s aria-labelledby points at, so labelling wires up without hand-rolling the id.
descriptionReadonlyArray<ChildAttribute>—Spread onto your description element (h.p([...description], [...])). Carries the framework-managed id referenced when hasDescription is true.
initialFocusReadonlyArray<ChildAttribute>—Spread onto the element that should receive focus when the dialog opens (h.input([...initialFocus])). A configured focusSelector takes precedence; to focus an element whose id you do not own, use focusSelector.
closeButtonReadonlyArray<ChildAttribute>—Spread onto an in-panel close control such as a Cancel button. Carries the click handler that closes the dialog, so a plain dismiss needs no parent message, and type="button" so a close control inside a form does not submit it.
isVisibleboolean—Derived from isOpen and the Animation transitionState. Render the backdrop and panel only while this is true.

OutMessage

Messages emitted to the parent through the optional outMessage field. Match on the OutMessage in the foldOutMessage of your Update.foldChild config.

NameTypeDefaultDescription
Opened{}—Emitted once the dialog has transitioned to open. Fires after update has processed RequestedOpen and isOpen reflects the new state.
Closed{}—Emitted once the dialog has transitioned to closed. Programmatic Dialog.close on an already-closed model is a no-op that does not re-emit, as is calling close while a leave animation is already in progress.