Popover

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJSApache-2.0
Installation mix mishka.ui.gen.headless popover
Source View Source

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)

heex
<.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.

Partdata-partState / ARIAPurpose
Triggertriggeraria-haspopup, aria-expanded, aria-controls, data-popup-open, data-pressed, data-disabledThe button that opens the popover; takes trigger_class.
Backdropbackdroparia-hiddenThe overlay behind the popup, rendered only when modal is not false; takes backdrop_class.
Popuppopuprole="dialog", aria-modal, aria-labelledby, aria-describedby, data-open, data-closed, data-side, data-align, data-starting-styleThe anchored panel itself; takes popup_class.
Arrowarrow-Optional pointer rendered inside the popup, aimed at the trigger; takes arrow_class.
Title / Descriptiontitle / descriptionaria-labelledby / aria-describedbyAnnounced to screen readers; wired automatically; they take title_class and description_class.
Contentcontent-Wraps the popup's default slot; takes content_class.
Footerfooter-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.