Detent web components
By Shane Logsdon

Overlays & menus

SettleModal

Implemented

Content scales and settles, never fades

detent-settle-modal

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-overlays
Group
Overlays & menus
Open medium Delete workspace

This action cannot be undone.

Focus enters the panel, Tab stays inside it, and Escape returns focus to the button that opened it. Body scroll is locked while the dialog is open.

5.7 KB gzip for the class module

Variants

Open small Open large
Small panel

Compact confirmation with the same focus contract.

Large panel

The widest preset is 32rem.

The panel scales from 0.94 through 1.02 to 1, and the inner content settles a layer deeper.

Recorded states

  • open recorded state; no setter
  • closed recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/settle-modal/define.js';

// or register it yourself
import { DetentSettleModal } from '/components/settle-modal/settle-modal.js';

Attributes

  • close-on-backdrop
  • hide-close-button
  • open
  • size

Properties

  • onClose

Slots

  • default
  • description
  • footer
  • title

Events

  • detent-close — {}

Example

Runnable example: site/examples/settle-modal.ts. The snippet below is generated from the catalogued attributes.

<detent-settle-modal
  close-on-backdrop
  hide-close-button
  open
  size="md"
></detent-settle-modal>

Property-only values

const element = document.querySelector('detent-settle-modal');
if (element !== null) {
  // onClose: () => void
  element.onClose = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/SettleModal.tsx sha256 63738cebe3c5…

Native files

The native module and its artifacts:

  • src/components/settle-modal/settle-modal.ts
  • src/components/settle-modal/styles.css
  • src/components/settle-modal/define.ts
  • docs/components/settle-modal.md
  • site/examples/settle-modal.ts
  • tests/browser/settle-modal.spec.ts
  • evidence/components/settle-modal.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
close-on-backdropcloseOnBackdropbooleantruePresence semantics: present means true, absent means false.
hide-close-buttonhideCloseButtonbooleanfalsePresence semantics: present means true, absent means false.
openopenbooleanundefinedPresence semantics: present means true, absent means false.
sizesize'sm' | 'md' | 'lg''md'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
onClose() => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-closeonClose() => voidComposed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
defaultConsumer content; rendered through a slot, never re-created.
descriptionConsumer content; rendered through a slot, never re-created.
footerConsumer content; rendered through a slot, never re-created.
titleConsumer content; rendered through a slot, never re-created.

CSS customisation

  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • onClose becomes the composed detent-close event with detail: {} and no cancelable/extra fields (PC-6.2/6.3). The onClose property is not part of the native surface.
  • React nodes become slots. title, description, children and footer are named/default slots; the title is a real <h2> and the description a real <p>. className is replaced by documented parts and custom properties.
  • A native modal <dialog> replaces the fixed inset-0 z-50 div. That buys the top layer, real inertness, native cancel for Escape and native focus containment across engines (PC-9.8/12.1/12.4). Upstream implemented none of the focus management its catalog accessibility entries claim.
  • Framer Motion becomes WAAPI keyframes with the recorded durations/curves. The three-layer choreography (panel overshoot, inner layer with its 0.05 s delay, backdrop fade) is preserved; interruption cancels and restarts instead of resuming a stale timeline; reduced motion skips it entirely (PC-10.1/10.4/10.5).
  • Scroll lock compensates the scrollbar width. Upstream only set document.body.style.overflow, which shifts the page by the scrollbar width on desktop. This port locks the document element and adds the measured padding-right (recorded upstream requirement, PC-12.6 correction).
  • Palette from tokens. Upstream used var(--surface)-style variables its dark-only site defined; this port reads --detent-color-* with light fallbacks so both schemes are legible (PC-9.5).
  • lucide-react's X is a local decorative inline SVG (PC-11.5).
  • No data-state attribute is invented: the documented states are observable through open and the native dialog's own open attribute.

What it does

A modal confirmation surface whose panel scales and settles into place instead of just fading. It renders as a real <dialog> in the browser's top layer, keeps focus inside while open, returns focus to the invoking element on close, and locks body scroll without a layout shift.

Upstream behaviour
  • Panel animates scale 0.94 -> 1.02 -> 1.0 with ease-soft curve for a tactile "settle"
  • Inner content has its own layered scale-up (0.98 -> 1.0) for a nested feel
  • Escape key dismisses; backdrop click closes by default (toggle via closeOnBackdrop)
  • Scroll lock while open; restores previous overflow on close
  • Three size presets (sm / md / lg) with optional title, description, and footer slots
Differences from upstream
  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • onClose becomes the composed detent-close event with detail: {} and no cancelable/extra fields (PC-6.2/6.3). The onClose property is not part of the native surface.
  • React nodes become slots. title, description, children and footer are named/default slots; the title is a real <h2> and the description a real <p>. className is replaced by documented parts and custom properties.
  • A native modal <dialog> replaces the fixed inset-0 z-50 div. That buys the top layer, real inertness, native cancel for Escape and native focus containment across engines (PC-9.8/12.1/12.4). Upstream implemented none of the focus management its catalog accessibility entries claim.
  • Framer Motion becomes WAAPI keyframes with the recorded durations/curves. The three-layer choreography (panel overshoot, inner layer with its 0.05 s delay, backdrop fade) is preserved; interruption cancels and restarts instead of resuming a stale timeline; reduced motion skips it entirely (PC-10.1/10.4/10.5).
  • Scroll lock compensates the scrollbar width. Upstream only set document.body.style.overflow, which shifts the page by the scrollbar width on desktop. This port locks the document element and adds the measured padding-right (recorded upstream requirement, PC-12.6 correction).
  • Palette from tokens. Upstream used var(--surface)-style variables its dark-only site defined; this port reads --detent-color-* with light fallbacks so both schemes are legible (PC-9.5).
  • lucide-react's X is a local decorative inline SVG (PC-11.5).
  • No data-state attribute is invented: the documented states are observable through open and the native dialog's own open attribute.
Previous: Bulk Action Bar Next: Sheet

Docs

Components

Project