Headless Core — Modal
Powers Modal and and-vanilla-modal internally. Note what
this factory doesn’t do: focus trapping and body-scroll locking are real DOM
operations, so they live in the consuming layer (@andersseen/web-components’s
utils/focus-trap.ts) — this factory only tracks isOpen and hands back the
ARIA props and event-handler logic.
import { createModal } from '@andersseen/headless-components/modal';
const modal = createModal({ closeOnEscape: true, closeOnOverlayClick: true, onOpenChange: isOpen => console.log('Modal:', isOpen),});
modal.subscribe(state => { if (state.isOpen) console.log('Modal opened');});
modal.actions.open();window.addEventListener('keydown', modal.handleKeyDown);Config
Section titled “Config”| Option | Type | Default | Notes |
|---|---|---|---|
defaultOpen | boolean | false | |
onOpenChange | (isOpen: boolean) => void | — | |
closeOnEscape | boolean | true | |
closeOnOverlayClick | boolean | true | |
disabled | boolean | false | Blocks open(); setting true while open closes it. |
label | string | 'Dialog' | Used as aria-label on the content element. |
isOpen, disabled.
Actions
Section titled “Actions”open(), close(), toggle(), setDisabled(v) — no queries bucket on this
factory (unlike most others); read modal.state.isOpen directly.
Prop-getters & handlers
Section titled “Prop-getters & handlers”| Getter | Returns |
|---|---|
getOverlayProps() | data-state, aria-hidden |
getContentProps() | role: 'dialog', aria-modal: true, aria-hidden, aria-label, data-state, tabindex: -1 |
getCloseButtonProps() | aria-label: 'Close', type: 'button' |
handleKeyDown(event) closes on Escape when closeOnEscape and the modal is
open — wire it to window, not the content element, since a modal’s content is
often not focused. handleOverlayClick() closes when closeOnOverlayClick;
call it from the overlay’s click handler.
Live example
Section titled “Live example”Next steps
Section titled “Next steps”Drawer is the same open/close/Escape/overlay shape with an
added placement side.