Overview of the Phoenix and LiveView Headless Alert Dialog Component
The Mishka Chelekom Headless Alert Dialog
ships zero opinionated styling
- only the semantic markup, full WAI-ARIA
wiring, a focus trap, and paired-presence state
(
data-open
/
data-closed
).
Unlike a plain dialog, the root sets
data-close-on-outside="false"
- a confirmation requires an explicit choice, so a backdrop click never dismisses it.
Every part - trigger, backdrop, popup, title, description,
content
and actions
- is addressable through a
*_class
attribute, so you style it entirely with your own Tailwind
classes while
the accessibility and behavior keep working underneath.
Base UI Alert Dialog example (Phoenix headless)
Discard draft?
You cannot undo this action.
<.alert_dialog id="baseui-alert_dialog-hero" trigger_class="flex h-8 items-center justify-center gap-2 border border-neutral-950 dark:border-white bg-white dark:bg-neutral-950 px-3 text-sm leading-none whitespace-nowrap font-normal text-red-700 dark:text-red-400 select-none hover:not-data-[disabled]:bg-neutral-100 dark:hover:not-data-[disabled]:bg-neutral-800 active:not-data-[disabled]:bg-neutral-200 dark:active:not-data-[disabled]:bg-neutral-700 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-[disabled]:border-neutral-500 data-[disabled]:text-neutral-500 disabled:border-neutral-500 disabled:text-neutral-500 dark:data-[disabled]:border-neutral-400 dark:data-[disabled]:text-neutral-400" backdrop_class="fixed inset-0 min-h-dvh bg-black opacity-20 transition-opacity duration-150 data-[closed]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-50 supports-[-webkit-touch-callout:none]:absolute" popup_class="fixed top-1/2 left-1/2 -mt-8 flex w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 bg-white dark:bg-neutral-950 p-4 text-neutral-950 dark:text-white border border-neutral-950 dark:border-white shadow-[0.25rem_0.25rem_0] shadow-black/12 dark:shadow-none transition-[scale,opacity] duration-100 ease-out data-[closed]:scale-[0.98] data-[closed]:opacity-0 data-[starting-style]:scale-[0.98] data-[starting-style]:opacity-0" title_class="text-base font-bold" description_class="text-sm text-neutral-600 dark:text-neutral-400" actions_class="flex justify-end gap-3" > <:trigger>Discard draft</:trigger> <:title>Discard draft?</:title> <:description>You can't undo this action.</:description> <:actions> <button type="button" data-close class="flex h-8 items-center justify-center gap-2 border border-neutral-950 dark:border-white bg-white dark:bg-neutral-950 px-3 text-sm leading-none whitespace-nowrap font-normal text-neutral-950 dark:text-white select-none hover:not-data-[disabled]:bg-neutral-100 dark:hover:not-data-[disabled]:bg-neutral-800 active:not-data-[disabled]:bg-neutral-200 dark:active:not-data-[disabled]:bg-neutral-700 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-[disabled]:border-neutral-500 data-[disabled]:text-neutral-500 disabled:border-neutral-500 disabled:text-neutral-500 dark:data-[disabled]:border-neutral-400 dark:data-[disabled]:text-neutral-400" > Cancel </button> <button type="button" data-close class="flex h-8 items-center justify-center gap-2 border border-neutral-950 dark:border-white bg-white dark:bg-neutral-950 px-3 text-sm leading-none whitespace-nowrap font-normal text-red-700 dark:text-red-400 select-none hover:not-data-[disabled]:bg-neutral-100 dark:hover:not-data-[disabled]:bg-neutral-800 active:not-data-[disabled]:bg-neutral-200 dark:active:not-data-[disabled]:bg-neutral-700 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-[disabled]:border-neutral-500 data-[disabled]:text-neutral-500 disabled:border-neutral-500 disabled:text-neutral-500 dark:data-[disabled]:border-neutral-400 dark:data-[disabled]:text-neutral-400" > Discard </button> </:actions> </.alert_dialog>
Parts & data attributes
Each part carries a stable
data-part
and a structural
chelekom-alert-dialog__*
class you can target, plus paired-presence state for open/closed transitions.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | - | data-open, data-closed, data-close-on-outside | Wraps the whole component; always renders data-close-on-outside="false". |
| Trigger | trigger | aria-haspopup, aria-expanded, aria-controls, data-popup-open, data-disabled | The button that opens the alert dialog. |
| Backdrop | backdrop | aria-hidden | The overlay behind the popup; never dismisses on click. |
| Popup | popup | role="alertdialog", aria-modal, aria-labelledby, aria-describedby, data-open, data-closed | The dialog panel itself; focus-trapped. |
| Title / Description | title / description | required; wired to aria-labelledby / aria-describedby | Announced to screen readers automatically. |
| Content | content | - | Optional inner body, rendered only when present. |
| Actions | actions | - | Footer for confirm/cancel buttons; add data-close to close. |
Accessibility
Follows the
WAI-ARIA Alert Dialog pattern
: focus is trapped inside the open popup and restored to the trigger on close,
Escape
closes it,
Tab
and
Shift+Tab
cycle within the trap, and - unlike a plain dialog - an outside click never dismisses it,
forcing an explicit choice. The title and description slots are required and announced
to screen readers.