R RenDS v0.13.0

Composite

Dialog Requires JS

A modal overlay built on the native <dialog> element. Focus trap, keyboard handling, click-outside dismiss, and smooth entry / exit animations come for free. Available as a CSS class set or as the <ren-dialog> Web Component with auto-wired triggers.

About

Overview

A dialog interrupts the user's current flow to confirm an action, gather a small piece of input, or surface a focused decision. RenDS uses the browser's native <dialog> element under the hood, so a lot of the hard parts — modal scroll lock, top-layer rendering, focus trapping, escape-to-close — come from the platform. The component layers smooth animations, mobile sheet behavior, and declarative auto-wiring on top.

Use a dialog when the user needs to make a deliberate choice before the rest of the UI continues. If the action is destructive or irreversible, use the alert variant so the dialog can't be dismissed by clicking the backdrop.

Don't reach for a dialog by default. If the decision can live on the page (an inline form, a popover, an expandable panel), prefer those. A modal blocks everything else and shouldn't be the answer to "where do I put this content?".

Parts

Anatomy

A dialog is composed of five regions. Three are required (container, body, footer with at least one action), two are conventional (header with title and close button, backdrop).

Assembled

1 Container

The dialog box itself — <dialog class="ren-dialog">. Sized by a width variant token (--dialog-max-width). Renders in the browser's top layer.

2 Backdrop

The dimmed scrim. Styled via ::backdrop. Click to dismiss (unless alert is set). Fades in with the dialog.

3 Header

.ren-dialog-header with .ren-dialog-title and an optional close button (data-dialog-close). Bottom-bordered, padded.

4 Body

.ren-dialog-body. Scrolls when content exceeds the dialog's max-height. Independent scrollbar so the page beneath stays locked.

5 Footer

.ren-dialog-footer with action buttons. Conventionally right-aligned, primary action last. Top-bordered.

Live

Demo

Click any trigger to open the dialog. Esc closes it; clicking outside the dialog closes it (except for alert dialogs). Tab cycles through the focusable elements without escaping.

<button data-dialog-trigger="dlg-confirm">Open dialog</button>

<ren-dialog id="dlg-confirm">
  <dialog class="ren-dialog">
    <div class="ren-dialog-header">
      <h2 class="ren-dialog-title">Confirm changes</h2>
      <button class="ren-dialog-close" data-dialog-close aria-label="Close">×</button>
    </div>
    <div class="ren-dialog-body">
      Save the edits to your profile?
    </div>
    <div class="ren-dialog-footer">
      <button class="ren-btn ren-btn-secondary" data-dialog-close>Cancel</button>
      <button class="ren-btn ren-btn-primary">Save</button>
    </div>
  </dialog>
</ren-dialog>

Shapes

Variants

Five sizes plus an alert mode. Default is md. Pick the smallest size that fits the content — bigger isn't friendlier.

Sizes
Alert
No backdrop dismiss · forces a button choice
No Escape
Esc is suppressed · use the in-dialog buttons

Reference

API

CSS classes

Apply directly to the <dialog> element. They work even if you don't use the Web Component (you'd just script open / close yourself).

Class Effect
.ren-dialog Base. Required on the <dialog> element. Provides background, border-radius, shadow, animations, and the backdrop styling.
.ren-dialog-header Top region. Flex row with a bottom border. Holds the title and close button.
.ren-dialog-title Heading inside the header. Set on the actual heading tag (<h2>, <h3>) so screen readers announce it correctly.
.ren-dialog-description Optional sub-title under the title. Muted color.
.ren-dialog-body Main content region. Scrolls independently when its content exceeds max-height: 85dvh.
.ren-dialog-footer Bottom region for action buttons. Top-bordered, right-aligned by default.
.ren-dialog-close The icon close button placed in the header. Square, ghost-styled, focus ring matches the system token.
.ren-dialog-sm / -md / -lg / -xl / -full Size modifiers. Set --dialog-max-width: 24rem · 32rem · 42rem · 56rem · 95dvw.
.ren-alert-dialog Alert flavor. Suppresses click-outside dismissal. Used by <ren-alert-dialog> internally.

Web Component attributes

Set on the <ren-dialog> wrapper. All are reactive — flipping them in JS opens, closes, or resizes the dialog without rebuilding the markup.

Attribute Type Default Notes
open boolean false Opens the dialog when present. Mirrors <dialog open> but routes through the component lifecycle (events, focus trap).
size "sm" | "md" | "lg" | "xl" | "full" "md" Width variant. Adds the matching .ren-dialog-* class to the inner dialog.
alert boolean false Disables backdrop click dismissal. The user must press a button to close.
no-escape boolean false Suppresses Esc closing the dialog. Use sparingly — it traps the user.

JavaScript methods

Available on every <ren-dialog> instance. All return Promises so you can await open / close transitions before continuing.

Method Description
show() / open() Opens the dialog. Returns a Promise that resolves once the entry animation finishes. Aliases.
close(returnValue?) Closes the dialog. Optional returnValue is forwarded to the ren-close event detail. Promise resolves after the exit animation.
isOpen Boolean getter. true while the dialog is visible.
dialog Getter that returns the underlying native <dialog> element if you need to call its DOM API directly.
const dialog = document.querySelector('ren-dialog'); await dialog.show(); // wait for it to be visible const result = await dialog.close('confirmed'); // returnValue surfaces in ren-close if (dialog.isOpen) { /* still open */ }

Events

Both events bubble and are composed. Listen on the wrapper or any ancestor.

Event Detail
ren-open Fired after the entry animation completes. event.detail is empty.
ren-close Fired after the exit animation completes. event.detail.returnValue contains the value passed to close(), if any.

Auto-wiring

You don't need to write JavaScript for the common case. Two declarative attributes wire opening and closing:

Attribute Effect
data-dialog-trigger="ID" On any element. Click opens the <ren-dialog> with the matching id.
data-dialog-close On any element inside the dialog. Click closes the parent dialog.

Inclusive by default

Accessibility

Because the component renders an actual <dialog> element, the platform handles most of the work. RenDS adds the focus management, motion preferences, and labelling conventions on top.

Keyboard

Tab Moves focus to the next focusable element inside the dialog. Cycles back to the first when it reaches the end.
Shift + Tab Moves focus backward, cycling at the start of the dialog.
Esc Closes the dialog. Suppressed when no-escape is set.
Enter Activates the focused button or submits the focused form, as the platform default. Not intercepted.

Focus management

  • On open, focus moves to the first focusable element inside the dialog. To override, mark a specific element with autofocus.
  • While open, focus is trapped inside the dialog — Tab and Shift+Tab cycle without leaving.
  • On close, focus returns to the element that opened the dialog (the trigger button). The user never has to find their place again.

Labelling

Always pair the dialog with an accessible name. The simplest path is to give the title an id and reference it via aria-labelledby:

<ren-dialog id="dlg-labelling" aria-labelledby="dlg-labelling-title"> <dialog class="ren-dialog"> <div class="ren-dialog-header"> <h2 class="ren-dialog-title" id="dlg-labelling-title">Delete file</h2> </div> ... </dialog> </ren-dialog>

If you also have descriptive prose under the title, link it with aria-describedby.

Motion

Entry and exit animations use the --duration-enter motion token. Under prefers-reduced-motion: reduce the transitions are disabled — the dialog appears and disappears instantly.

Use the alert variant for destructive confirmations. A backdrop click on a regular dialog dismisses it silently — fine for most cases, dangerous when the user is about to delete something. The alert variant forces an explicit button choice.

Patterns

Examples

Confirmation

The classic: a dialog asks the user to confirm a non-trivial action.

<button data-dialog-trigger="dlg-save">Save</button> <ren-dialog id="dlg-save" aria-labelledby="save-title"> <dialog class="ren-dialog"> <div class="ren-dialog-header"> <h2 class="ren-dialog-title" id="save-title">Save changes?</h2> </div> <div class="ren-dialog-body"> Your edits will be visible to the team immediately. </div> <div class="ren-dialog-footer"> <button class="ren-btn ren-btn-secondary" data-dialog-close>Cancel</button> <button class="ren-btn ren-btn-primary">Save</button> </div> </dialog> </ren-dialog>

Destructive (alert)

Backdrop click is suppressed; the user must press one of the two buttons.

<button class="ren-btn ren-btn-danger" data-dialog-trigger="dlg-delete">Delete account</button> <ren-alert-dialog id="dlg-delete" aria-labelledby="delete-title"> <dialog class="ren-dialog ren-alert-dialog"> <div class="ren-dialog-header"> <h2 class="ren-dialog-title" id="delete-title">Delete this account?</h2> </div> <div class="ren-dialog-body"> All projects, files, and history will be permanently removed. This cannot be undone. </div> <div class="ren-dialog-footer"> <button class="ren-btn ren-btn-secondary" data-dialog-close>Keep account</button> <button class="ren-btn ren-btn-danger">Delete account</button> </div> </dialog> </ren-alert-dialog>

Form dialog

Using a dialog to collect a small piece of input. Submit the form to close.

<button data-dialog-trigger="dlg-rename">Rename project</button> <ren-dialog id="dlg-rename" aria-labelledby="dlg-rename-title"> <dialog class="ren-dialog ren-dialog-sm"> <form method="dialog"> <div class="ren-dialog-header"> <h2 class="ren-dialog-title" id="dlg-rename-title">Rename project</h2> </div> <div class="ren-dialog-body"> <label for="project-name" class="ren-field-label">New name</label> <input id="project-name" name="project-name" class="ren-input" autofocus required> </div> <div class="ren-dialog-footer"> <button class="ren-btn ren-btn-secondary" type="button" data-dialog-close>Cancel</button> <button class="ren-btn ren-btn-primary" type="submit">Rename</button> </div> </form> </dialog> </ren-dialog>

Reading the result

When you need to know which button closed the dialog, listen to ren-close and pass a returnValue.

const dialog = document.querySelector('#dlg-save'); dialog.querySelector('.ren-btn-primary') .addEventListener('click', () => dialog.close('save')); dialog.querySelector('.ren-btn-secondary') .addEventListener('click', () => dialog.close('cancel')); dialog.addEventListener('ren-close', (e) => { if (e.detail.returnValue === 'save') { persist(); } });

Confirm changes

Save the edits to your profile? You can revert any time from settings.

Delete this account?

All projects, files, and history will be permanently removed. This cannot be undone.

Small dialog

24 rem (384 px) max-width. For tight, single-decision dialogs.

Medium dialog (default)

32 rem (512 px) max-width. The everyday default — fits a paragraph of context plus a couple of buttons.

Large dialog

42 rem (672 px) max-width. Use for forms with multiple fields or list-pickers.

Extra-large dialog

56 rem (896 px) max-width. For media browsers, multi-step wizards, or rich previews.

Full-bleed dialog

95 dvw — almost edge-to-edge. Reserve for full-screen takeovers (image lightbox, focus modes).

Critical step

Esc won't close this. Use one of the buttons below to leave. (You'd reserve this for moments where dismissing accidentally would lose work — e.g. mid-payment-flow.)