Overview of the Phoenix and LiveView Headless Popover Component
The Mishka Chelekom Headless Popover
ships zero opinionated styling
- only the semantic markup, full WAI-ARIA
wiring, anchored positioning (side/align with edge-flip and viewport clamp), and
paired-presence state
(
data-open
/
data-closed
).
Every part - trigger, backdrop, popup, arrow, title, description,
content
and close
- 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 Popover example (Phoenix headless)
Notifications
You are all caught up. Good job!
<.popover id="baseui-popover-hero" side_offset={8} trigger_class="flex h-8 items-center justify-center border border-neutral-950 dark:border-white bg-white dark:bg-neutral-950 px-3 text-sm font-normal whitespace-nowrap 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 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 data-[popup-open]:bg-neutral-100 dark:data-[popup-open]:bg-neutral-800 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white" popup_class="group relative flex h-[var(--popup-height,auto)] w-[var(--popup-width,auto)] max-w-[500px] flex-col gap-1 origin-[var(--transform-origin)] bg-white dark:bg-neutral-950 p-3 text-neutral-950 dark:text-white outline-none 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-[ending-style]:scale-[0.98] data-[ending-style]:opacity-0 data-[starting-style]:scale-[0.98] data-[starting-style]:opacity-0" title_class="text-sm font-bold" description_class="text-sm text-neutral-600 dark:text-neutral-400" arrow_class="relative block w-3 h-1.5 overflow-clip group-data-[side=bottom]:top-[-6px] group-data-[side=left]:right-[-9px] group-data-[side=left]:rotate-90 group-data-[side=right]:left-[-9px] group-data-[side=right]:-rotate-90 group-data-[side=top]:bottom-[-6px] group-data-[side=top]:rotate-180 before:content-[''] before:absolute before:bottom-0 before:left-1/2 before:w-[calc(6px*sqrt(2))] before:h-[calc(6px*sqrt(2))] before:bg-white dark:before:bg-neutral-950 before:border before:border-neutral-950 dark:before:border-white before:[transform:translate(-50%,50%)_rotate(45deg)]" > <:trigger>Notifications</:trigger> <:arrow></:arrow> <:title>Notifications</:title> <:description>You are all caught up. Good job!</:description> </.popover>
Parts & data attributes
Each part carries a stable
data-part
and a structural
chelekom-popover__*
class you can target, plus paired-presence state for open/closed transitions.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Trigger | trigger | aria-haspopup, aria-expanded, aria-controls, data-popup-open, data-pressed, data-disabled | The button that opens the popover. |
| Backdrop | backdrop | aria-hidden | The overlay behind the popup, rendered only when modal is not false. |
| Popup | popup | role="dialog", aria-modal, aria-labelledby, aria-describedby, data-open, data-closed, data-side, data-align, data-starting-style | The anchored panel itself. |
| Arrow | arrow | - | Optional pointer rendered inside the popup, aimed at the trigger. |
| Title / Description | title / description | aria-labelledby / aria-describedby | Announced to screen readers; wired automatically. |
| Close | close | - |
Optional footer actions; use
data-close
on a button to close.
|
Accessibility
Follows the
WAI-ARIA Authoring Practices
for a non-modal dialog anchored to its trigger: focus moves into the popup on open
(
initial_focus
) and is restored to the
trigger (or
final_focus
) on close,
Escape
closes it, and an outside click dismisses it (both configurable). Setting
modal
to
true
or
trap-focus
traps Tab cycling inside the popup while it is open.