Dialog
v0.0.10
mix mishka.ui.gen.headless dialog
Overview of the Phoenix and LiveView Headless Dialog Component
The Mishka Chelekom Headless Dialog ships zero opinionated styling - only the semantic markup, full WAI-ARIA wiring, a focus trap, scroll lock, and paired-presence state (data-open/data-closed). Every part - trigger, backdrop, viewport, popup, title, description, content and footer - 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 Dialog example (Phoenix headless)
Base UI Dialog example (Phoenix headless)
<.dialog
id="baseui-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 font-normal text-neutral-950 dark:text-white select-none hover:bg-neutral-100 dark:hover:bg-neutral-800"
backdrop_class="fixed inset-0 min-h-dvh bg-black opacity-20 transition-opacity duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-50"
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 transition-[scale,opacity] duration-100 ease-out data-[ending-style]:scale-[0.98] data-[ending-style]: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"
footer_class="flex justify-end gap-3"
>
<:trigger>View notifications</:trigger>
<:title>Notifications</:title>
<:description>You are all caught up. Good job!</:description>
<p class="text-sm text-neutral-600 dark:text-neutral-400">Your next summary arrives on Monday.</p>
<:close>
<button
type="button"
data-close
class="flex h-8 items-center gap-2 border border-neutral-950 dark:border-white bg-white dark:bg-neutral-950 px-3 text-sm text-neutral-950 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800"
>
Close
</button>
</:close>
</.dialog>Parts & data attributes
Each part carries a stable data-part and a structural chelekom-dialog__* class you can target, plus paired-presence state for open/closed transitions.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Trigger | trigger | aria-haspopup, aria-expanded, data-popup-open | The button that opens the dialog. |
| Backdrop | backdrop | data-open, data-closed, data-starting-style, data-ending-style | The overlay behind the popup; carries the transition state. |
| Popup | popup | role="dialog", focus-trapped | The dialog panel itself. |
| Title / Description | title / description | aria-labelledby / aria-describedby | Announced to screen readers; wired automatically. |
Accessibility
Follows the WAI-ARIA Dialog (Modal) pattern: focus is trapped inside the open dialog and restored to the trigger on close, Escape closes it, and an outside click dismisses it (both configurable). The title and description slots are announced to screen readers.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id (anchors aria relationships) |
open
|
boolean
|
false
| — | No | Initial/controlled open state |
modal
|
any
|
true
| — | No | true | false | "trap-focus" — focus trap / scroll lock / backdrop |
dismissible
|
boolean
|
true
| — | No | Whether an outside click dismisses |
close_on_escape
|
boolean
|
true
| — | No | Whether Escape closes |
disabled
|
boolean
|
false
| — | No | Disable the trigger (data-disabled) |
on_open_change
|
string
| — | — | No | LiveView event pushed on open/close ({open}) |
on_open_change_target
|
string
| — | — | No | Optional pushEventTo target for on_open_change |
initial_focus
|
string
| — | — | No | Selector focused on open (default: first focusable) |
final_focus
|
string
| — | — | No | Selector focused on close (default: the opener) |
labelledby
|
string
| — | — | No | Override aria-labelledby |
describedby
|
string
| — | — | No | Override aria-describedby |
class
|
any
| — | — | No | Extra classes for the root |
trigger_class
|
any
| — | — | No | Extra classes for the trigger button |
backdrop_class
|
any
| — | — | No | Extra classes for the backdrop |
viewport_class
|
any
| — | — | No | Extra classes for the viewport |
popup_class
|
any
| — | — | No | Extra classes for the popup |
title_class
|
any
| — | — | No | Extra classes for the title |
description_class
|
any
| — | — | No | Extra classes for the description |
content_class
|
any
| — | — | No | Extra classes for the inner content wrapper |
footer_class
|
any
| — | — | No | Extra classes for the close/footer wrapper |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
trigger
| No | The element that opens the dialog |
title
| No | Accessible title (wired to aria-labelledby) |
description
| No | Accessible description (wired to aria-describedby) |
inner_block
| Yes | Dialog body |
close
| No | Footer / close actions (use data-close on a button to close) |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
dialog
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless dialog