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- Related
- Usage rulesTheme previews
Installation
- npm
- Yarn
- pnpm
npm install @rcarls/rc-webcomponents@next
yarn add @rcarls/rc-webcomponents@next
pnpm add @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 impliesaria-modal="true".- The element captures the focused element before opening and restores focus to it on close,
falling back to
document.bodyif that element was removed. - Escape closes the dialog through
rc-dialog-request-close, so close guards still apply. - The dialog needs
aria-labelledbyoraria-label; development builds warn when both are missing. Withrole="alertdialog", also pointaria-describedbyat 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
| Property | Markup | Type | Default | Description |
|---|---|---|---|---|
movable | movable | boolean | false | Allow 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`. |
moveHandle | move-handle | string | '' | CSS selector (within the inner `<dialog>`) for the drag handle element. |
moveBounds | move-bounds | 'viewport' | 'parent' | 'viewport' | Area that moving and resizing stay inside: `viewport`, or `parent` for the element that contains `<rc-dialog>`. |
moveStep | move-step | number | 4 | Keyboard arrow-key step in px for moving. |
resize | resize | 'none' | 'both' | 'horizontal' | 'vertical' | Not specified | Enable 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. |
resizeOrigin | resize-origin | ResizeOrigin | '' | Origin for resize edge hit-testing or explicit handles. Empty preserves the historical free edge-detection behavior. |
resizeHandle | resize-handle | string | '' | 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|..."` |
resizeThreshold | resize-threshold | number | 8 | Edge hit-test thickness in px for resize detection. |
resizeStep | resize-step | number | 4 | Keyboard arrow-key step in px for resizing. |
closedBy | closed-by | 'any' | 'closerequest' | 'none' | '' | Not specified | Proxied 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. |
variant | variant | RCDialogVariant | 'standard' | Surface presentation. Fullscreen geometry follows the visual viewport while open; a side sheet docks to the inline end at full block size. |
modal | JS property only | boolean | true | Whether controlled open state uses `showModal()` instead of `show()`. JavaScript property only; no attribute is observed. |
open | open | boolean | Not specified | Whether the inner `<dialog>` is currently open. Host writes update silently. |
defaultOpen | default-open | boolean | Not specified | Initial uncontrolled open state. |
returnValue | JS property only | string | Not specified | The return value set when the dialog was closed. |
Methods
| Method | Description |
|---|---|
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
| Event | Detail type | Description |
|---|---|---|
rc-dialog-open | CustomEvent | Fired when the dialog opens via `showModal()` or `show()`. |
rc-dialog-toggle | CustomEvent | Fired when user/native interaction changes open state. |
rc-dialog-request-close | No detail type documented | Fired before the dialog closes (cancellable). |
rc-dialog-cancel | No detail type documented | Mirrors the inner `<dialog>` cancel event when the close was not prevented. Backward-compatible alias for `rc-dialog-request-close`. |
rc-dialog-close | No detail type documented | Mirrors the inner `<dialog>` close event. `detail: { returnValue: string }` |
Slots
| Name | Description |
|---|---|
(default) | Place a `<dialog>` element with your content here. |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--rc-dialog-inline-size | fit-content | Standard surface preferred inline size. Set `100%` for a task dialog whose fluid content would otherwise shrink it to its minimum. |
--rc-dialog-min-inline-size | auto | Standard surface minimum inline size. |
--rc-dialog-max-inline-size | none | Standard surface maximum inline size. |
--rc-dialog-max-block-size | calc(100dvb - 2rem) | Standard surface block bound. The surface never scrolls itself, so a scrollable dialog should scroll a dedicated inner region. |
--rc-dialog-padding | 1em | Standard surface padding. |
--rc-dialog-border | 0 | Surface border. |
--rc-dialog-radius | 0 | Surface corner radius. |
--rc-dialog-background | Canvas | Surface background. |
--rc-dialog-color | CanvasText | Surface foreground. |
--rc-dialog-shadow | none | Surface shadow. |
--rc-dialog-scrim | color-mix(in srgb, CanvasText 32%, transparent) | Modal backdrop color. |
--rc-dialog-fullscreen-padding-block | Not specified | Fullscreen 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-inline | Not specified | Fullscreen inline padding, safe-area aware. |
--rc-dialog-fullscreen-background | Not specified | Fullscreen background. Defers to `--rc-dialog-background` when unset. |
--rc-dialog-side-inline-size | min(100%, 25rem) | Side sheet inline size. |
--rc-dialog-side-background | Not specified | Side sheet background. Defers to `--rc-dialog-background` when unset. |
--rc-dialog-side-radius | Not specified | Side sheet start-corner radius. Defers to `--rc-dialog-radius` when unset. |
--rc-dialog-side-shadow | Not specified | Side sheet shadow. Defers to `--rc-dialog-shadow` when unset. |
--rc-dialog-visual-viewport-left | Not specified | Read-only fullscreen visual viewport left edge. |
--rc-dialog-visual-viewport-top | Not specified | Read-only fullscreen visual viewport top edge. |
--rc-dialog-visual-viewport-width | Not specified | Read-only fullscreen visual viewport width. |
--rc-dialog-visual-viewport-height | Not specified | Read-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. |