Popover
v0.0.10
mix mishka.ui.gen.headless popover
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 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.
The root takes class. The parts take trigger_class, backdrop_class, popup_class, arrow_class, title_class, description_class, content_class and footer_class. The backdrop is drawn only when modal is not false, and the footer, which holds the close slot, only when that slot has content.
Base UI Popover example (Phoenix headless)
Base UI Popover example (Phoenix headless)
Notifications
You are all caught up. Good job!
Your next summary arrives on Monday.
<.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>
<p class="text-sm text-neutral-600 dark:text-neutral-400">Your next summary arrives on Monday.</p>
</.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; takes trigger_class. |
| Backdrop | backdrop | aria-hidden | The overlay behind the popup, rendered only when modal is not false; takes backdrop_class. |
| 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; takes popup_class. |
| Arrow | arrow | - | Optional pointer rendered inside the popup, aimed at the trigger; takes arrow_class. |
| Title / Description | title / description | aria-labelledby / aria-describedby | Announced to screen readers; wired automatically; they take title_class and description_class. |
| Content | content | - | Wraps the popup's default slot; takes content_class. |
| Footer | footer | - | Holds the close slot and is drawn only when it has content; takes footer_class. Put data-close on a button to close the popover. |
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.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id (anchors aria relationships) |
open
|
boolean
|
false
| — | No | Initial/controlled open state |
side
|
string
|
bottom
|
toprightbottomleft
| No | Preferred side to anchor the popup |
align
|
string
|
center
|
startcenterend
| No | Alignment along the side |
side_offset
|
integer
|
8
| — | No | Gap between trigger and popup (px) |
align_offset
|
integer
|
0
| — | No | Offset along the alignment axis (px) |
modal
|
any
|
false
| — | No | false | true | "trap-focus" — focus trap / scroll lock / backdrop |
open_on_hover
|
boolean
|
false
| — | No | Open on trigger hover (with delay) |
delay
|
integer
|
300
| — | No | Hover open delay (ms) |
close_delay
|
integer
|
0
| — | No | Hover close delay (ms) |
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 |
initial_focus
|
string
| — | — | No | Selector focused on open (default: first focusable / the popup) |
final_focus
|
string
| — | — | No | Selector focused on close (default: the trigger) |
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 |
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 part |
popup_class
|
any
| — | — | No | Extra classes for the popup part |
title_class
|
any
| — | — | No | Extra classes for the title part |
description_class
|
any
| — | — | No | Extra classes for the description part |
content_class
|
any
| — | — | No | Extra classes for the content part |
arrow_class
|
any
| — | — | No | Extra classes for the arrow part |
backdrop_class
|
any
| — | — | No | — |
footer_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
trigger
| Yes | The button that opens the popover |
arrow
| No | Optional arrow pointing at the trigger (rendered inside the popup) |
title
| No | Accessible title (wired to aria-labelledby) |
description
| No | Accessible description (wired to aria-describedby) |
inner_block
| Yes | Popup content |
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
|
popover
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless popover