Skip to main content

rc-dialog

Draggable, resizable wrapper for a native <dialog>, following the WAI-ARIA Dialog Modal pattern.

Package
@rcarls/rc-dialog
Element
<rc-dialog>
Native dependency
Requires a direct child native dialog
State model
Open state follows the native dialog
Main events
rc-dialog-openrc-dialog-togglerc-dialog-request-closerc-dialog-cancelrc-dialog-close

Installation​

npm install @rcarls/rc-webcomponents@next
import '@rcarls/rc-webcomponents/rc-dialog/define';

Usage​

Place a native <dialog> as the direct child and name it with aria-labelledby or aria-label. Open and close it through the rc-dialog element.

<rc-dialog id="my-dialog">
<dialog aria-labelledby="dlg-title">
<h2 id="dlg-title">Hello</h2>
<p>Dialog content.</p>
<button type="button" onclick="document.querySelector('#my-dialog').close()">Close</button>
</dialog>
</rc-dialog>
<button type="button" onclick="document.querySelector('#my-dialog').showModal()">Open</button>

Fullscreen variant​

Use variant="fullscreen" for a surface that follows the current visual viewport, including browser zoom offsets and software-keyboard resizing. Move and resize behavior is disabled for this variant. The consumer continues to author the native dialog content and accessible close action.

<rc-dialog variant="fullscreen">
<dialog aria-labelledby="picker-title">
<header>
<button aria-label="Close">✕</button>
<h2 id="picker-title">Choose categories</h2>
<button>Done</button>
</header>
</dialog>
</rc-dialog>

Side sheet variant​

Use variant="side" for a modal side sheet: the surface docks to the inline end at full block size, pads every edge for the physical safe-area insets, and rounds only its start corners. Move and resize behavior is disabled for this variant, as for fullscreen. A theme sets its inline size, background, start-corner radius, and shadow through --rc-dialog-side-inline-size, --rc-dialog-side-background, --rc-dialog-side-radius, and --rc-dialog-side-shadow.

<rc-dialog variant="side" closed-by="any">
<dialog aria-labelledby="filters-title">
<h2 id="filters-title">Filters</h2>
<form method="dialog"><button value="apply">Apply</button></form>
</dialog>
</rc-dialog>

Live demo​

Accessibility​

rc-dialog relies on the native <dialog>:

  • showModal() traps Tab and Shift+Tab inside the dialog and implies aria-modal="true".
  • The element captures the focused element before opening and restores focus to it on close, falling back to document.body if that element was removed.
  • Escape closes the dialog through rc-dialog-request-close, so close guards still apply.
  • The dialog needs aria-labelledby or aria-label; development builds warn when both are missing. With role="alertdialog", also point aria-describedby at the message.

See the WAI-ARIA Dialog (Modal) pattern.

Theming​

The default demo mode shows the component without a package theme. Use the shared preview controls on this page to compare inherited, light, and dark color schemes or to apply the optional Material theme only inside the demo frames.

Set --rc-dialog-min-inline-size, --rc-dialog-max-inline-size, and --rc-dialog-max-block-size on <rc-dialog> or a theme scope to bound the standard surface. The Material theme supplies its viewport-clamped 280px to 560px dialog width. The surface fits its content inside those bounds; set --rc-dialog-inline-size: 100% for a dialog whose fluid content would otherwise shrink it to its minimum. --rc-dialog-scrim customizes the modal backdrop.

API​

Properties​

PropertyMarkupTypeDefaultDescription
movablemovablebooleanfalseAllow the dialog to be moved by dragging. Use `move-handle` to restrict dragging to a specific child element (e.g. a titlebar). Reactive: changing this or any other `move-*` option after setup rebuilds the move behavior and re-queries `move-handle`.
moveHandlemove-handlestring''CSS selector (within the inner `<dialog>`) for the drag handle element.
moveBoundsmove-bounds'viewport' | 'parent''viewport'Area that moving and resizing stay inside: `viewport`, or `parent` for the element that contains `<rc-dialog>`.
moveStepmove-stepnumber4Keyboard arrow-key step in px for moving.
resizeresize'none' | 'both' | 'horizontal' | 'vertical'Not specifiedEnable resizing, mirroring the CSS `resize` property values. Reactive: changing this or any other `resize-*` option after setup rebuilds the resize behavior and re-queries `resize-handle` matches, so `'none'` turns resizing off and restores the handles' focusability.
resizeOriginresize-originResizeOrigin''Origin for resize edge hit-testing or explicit handles. Empty preserves the historical free edge-detection behavior.
resizeHandleresize-handlestring''CSS selector, scoped to the inner `<dialog>`, for one or more explicit resize handles. Handles may override host behavior with: - `data-rc-dialog-resize-axis="x|y|both"` - `data-rc-dialog-resize-origin="top|right|bottom|left|top-left|..."`
resizeThresholdresize-thresholdnumber8Edge hit-test thickness in px for resize detection.
resizeStepresize-stepnumber4Keyboard arrow-key step in px for resizing.
closedByclosed-by'any' | 'closerequest' | 'none' | ''Not specifiedProxied to the inner `<dialog closedby="...">` attribute. - `'any'` — Escape OR backdrop click closes the dialog. Where the browser lacks native `closedby`, a backdrop click calls `requestClose()`. - `'closerequest'` — Escape only (browser default for modal dialogs). - `'none'` — Only programmatic `close()` / `requestClose()`. - `''` — Attribute absent; the browser default applies.
variantvariantRCDialogVariant'standard'Surface presentation. Fullscreen geometry follows the visual viewport while open; a side sheet docks to the inline end at full block size.
modalJS property onlybooleantrueWhether controlled open state uses `showModal()` instead of `show()`. JavaScript property only; no attribute is observed.
openopenbooleanNot specifiedWhether the inner `<dialog>` is currently open. Host writes update silently.
defaultOpendefault-openbooleanNot specifiedInitial uncontrolled open state.
returnValueJS property onlystringNot specifiedThe return value set when the dialog was closed.

Methods​

MethodDescription
showModal()Opens the inner `<dialog>` as a modal and fires `rc-dialog-open`.
show()Opens the inner `<dialog>` (non-modal) and fires `rc-dialog-open`.
close(returnValue?: string)Closes the inner `<dialog>`, optionally setting a return value.
requestClose(returnValue?: string)Requests the inner `<dialog>` to close, firing a cancelable `cancel` event first.

Events​

EventDetail typeDescription
rc-dialog-openCustomEventFired when the dialog opens via `showModal()` or `show()`.
rc-dialog-toggleCustomEventFired when user/native interaction changes open state.
rc-dialog-request-closeNo detail type documentedFired before the dialog closes (cancellable).
rc-dialog-cancelNo detail type documentedMirrors the inner `<dialog>` cancel event when the close was not prevented. Backward-compatible alias for `rc-dialog-request-close`.
rc-dialog-closeNo detail type documentedMirrors the inner `<dialog>` close event. `detail: { returnValue: string }`

Slots​

NameDescription
(default)Place a `<dialog>` element with your content here.

CSS Custom Properties​

PropertyDefaultDescription
--rc-dialog-inline-sizefit-contentStandard surface preferred inline size. Set `100%` for a task dialog whose fluid content would otherwise shrink it to its minimum.
--rc-dialog-min-inline-sizeautoStandard surface minimum inline size.
--rc-dialog-max-inline-sizenoneStandard surface maximum inline size.
--rc-dialog-max-block-sizecalc(100dvb - 2rem)Standard surface block bound. The surface never scrolls itself, so a scrollable dialog should scroll a dedicated inner region.
--rc-dialog-padding1emStandard surface padding.
--rc-dialog-border0Surface border.
--rc-dialog-radius0Surface corner radius.
--rc-dialog-backgroundCanvasSurface background.
--rc-dialog-colorCanvasTextSurface foreground.
--rc-dialog-shadownoneSurface shadow.
--rc-dialog-scrimcolor-mix(in srgb, CanvasText 32%, transparent)Modal backdrop color.
--rc-dialog-fullscreen-padding-blockNot specifiedFullscreen block padding. Defaults to a safe-area-aware inset, which is the one value the fullscreen variant genuinely needs of its own.
--rc-dialog-fullscreen-padding-inlineNot specifiedFullscreen inline padding, safe-area aware.
--rc-dialog-fullscreen-backgroundNot specifiedFullscreen background. Defers to `--rc-dialog-background` when unset.
--rc-dialog-side-inline-sizemin(100%, 25rem)Side sheet inline size.
--rc-dialog-side-backgroundNot specifiedSide sheet background. Defers to `--rc-dialog-background` when unset.
--rc-dialog-side-radiusNot specifiedSide sheet start-corner radius. Defers to `--rc-dialog-radius` when unset.
--rc-dialog-side-shadowNot specifiedSide sheet shadow. Defers to `--rc-dialog-shadow` when unset.
--rc-dialog-visual-viewport-leftNot specifiedRead-only fullscreen visual viewport left edge.
--rc-dialog-visual-viewport-topNot specifiedRead-only fullscreen visual viewport top edge.
--rc-dialog-visual-viewport-widthNot specifiedRead-only fullscreen visual viewport width.
--rc-dialog-visual-viewport-heightNot specifiedRead-only fullscreen visual viewport height. Fullscreen geometry, meaning position, size, margin, radius, and shadow, is structural rather than themable: filling the visual viewport is what keeps the surface correct under browser zoom and a software keyboard. While a keyboard closes, the surface takes only growth, so the shorter transitional sizes some mobile browsers report do not flash through. Side sheet placement is structural too: the surface docks to the inline end at full block size, with physical safe-area padding on every edge; only its inline size, background, start-corner radius, and shadow are themable. Neither docked variant moves or resizes, so `movable` and `resize` apply to the standard variant only. This element also writes `--rc-dialog-visual-viewport-left`, `-top`, `-width`, and `-height` onto itself while a fullscreen dialog is open. Those are geometry outputs, not theme inputs: read them if something inside the dialog needs the same measurements, but setting them does not move the dialog, and the component overwrites them on the next viewport change. They are candidates for private `--_rc-dialog-*` names in the next compatible API revision.