Overlays & menus
SettleModal
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
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
Compact confirmation with the same focus contract.
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
openrecorded state; no setterclosedrecorded state; no setterreduced motionrecorded 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-backdrophide-close-buttonopensize
Properties
onClose
Slots
defaultdescriptionfootertitle
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.tsxsha256 63738cebe3c5…
Native files
The native module and its artifacts:
src/components/settle-modal/settle-modal.tssrc/components/settle-modal/styles.csssrc/components/settle-modal/define.tsdocs/components/settle-modal.mdsite/examples/settle-modal.tstests/browser/settle-modal.spec.tsevidence/components/settle-modal.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
close-on-backdrop | closeOnBackdrop | boolean | true | Presence semantics: present means true, absent means false. |
hide-close-button | hideCloseButton | boolean | false | Presence semantics: present means true, absent means false. |
open | open | boolean | undefined | Presence semantics: present means true, absent means false. |
size | size | 'sm' | 'md' | 'lg' | 'md' | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
onClose | () => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-close | onClose | () => void | Composed, bubbles; fires only for a user action. |
Slots
| Slot | Notes |
|---|---|
default | Consumer content; rendered through a slot, never re-created. |
description | Consumer content; rendered through a slot, never re-created. |
footer | Consumer content; rendered through a slot, never re-created. |
title | Consumer 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.