Popover
NewOverview
A floating panel for interactive content, opened by click, hover, or focus.
Put exactly one interactive element in trigger
and interactive flyout controls in content. The hook adds the expanded-state and dialog relationships, then keeps the panel local to the browser for immediate interaction. Use
Tooltip
for non-interactive descriptions or contextual help.
Website redesign
<div class="flex justify-center">
<Popover.popover id="project-actions-popover" label="Project actions">
<:trigger>
<Button.button id="project-actions-trigger" kind={:outline}>Manage project</Button.button>
</:trigger>
<:content>
<div class="grid gap-3">
<h3 class="font-semibold">Website redesign</h3>
<div class="flex flex-wrap gap-2">
<Button.button
size={:sm}
phx-click="demo-action"
phx-value-action="Editing website redesign"
>
Edit project
</Button.button>
<Button.button
size={:sm}
kind={:destructive}
phx-click="demo-action"
phx-value-action="Archive confirmation opened"
>
Archive
</Button.button>
</div>
</div>
</:content>
</Popover.popover>
</div>
Open on hover
Set open_on_hover
to reveal flyout actions during pointer exploration. The panel remains open while the pointer moves between the trigger and its interactive content, and clicking remains available for touch input.
<div class="flex justify-center">
<Popover.popover
id="reviewer-popover"
label="Reviewer details"
side={:right}
open_on_hover
>
<:trigger>
<Button.button id="reviewer-popover-trigger" kind={:outline}>Maya Chen</Button.button>
</:trigger>
<:content>
<div class="grid gap-3">
<strong>Maya Chen</strong>
<span class="text-muted-foreground">Design systems · Detroit</span>
<div class="flex flex-wrap gap-2">
<Button.button
size={:sm}
phx-click="demo-action"
phx-value-action="Opening Maya Chen’s profile"
>
View profile
</Button.button>
<Button.button
size={:sm}
kind={:outline}
phx-click="demo-action"
phx-value-action="Starting a message to Maya Chen"
>
Message
</Button.button>
</div>
</div>
</:content>
</Popover.popover>
</div>
Open on focus
Set open_on_focus
to reveal interactive navigation flyouts when their triggers receive keyboard focus. Focus can move into each flyout’s links without closing it.
<nav aria-label="Example navigation" class="flex flex-wrap justify-center gap-1">
<Popover.popover id="product-nav" label="Product navigation" side={:bottom_start} open_on_focus>
<:trigger>
<Button.button id="product-nav-trigger" kind={:ghost}>Product</Button.button>
</:trigger>
<:content>
<div class="grid gap-1">
<strong class="mb-1">Product</strong>
<Link.link navigate="/" class="rounded-lg px-2 py-1.5 hover:bg-muted hover:no-underline">Component catalog</Link.link>
<Link.link navigate="/pricing" class="rounded-lg px-2 py-1.5 hover:bg-muted hover:no-underline">Pricing</Link.link>
</div>
</:content>
</Popover.popover>
<Popover.popover id="resources-nav" label="Resources navigation" side={:bottom_start} open_on_focus>
<:trigger>
<Button.button id="resources-nav-trigger" kind={:ghost}>Resources</Button.button>
</:trigger>
<:content>
<div class="grid gap-1">
<strong class="mb-1">Resources</strong>
<Link.link navigate="/docs/installation" class="rounded-lg px-2 py-1.5 hover:bg-muted hover:no-underline">Installation</Link.link>
<Link.link navigate="/docs/floating-ui" class="rounded-lg px-2 py-1.5 hover:bg-muted hover:no-underline">Floating UI</Link.link>
</div>
</:content>
</Popover.popover>
<Popover.popover id="company-nav" label="Company navigation" side={:bottom_start} open_on_focus>
<:trigger>
<Button.button id="company-nav-trigger" kind={:ghost}>Company</Button.button>
</:trigger>
<:content>
<div class="grid gap-1">
<strong class="mb-1">Company</strong>
<Link.link navigate="/roadmap" class="rounded-lg px-2 py-1.5 hover:bg-muted hover:no-underline">Roadmap</Link.link>
<Link.link navigate="/accessibility" class="rounded-lg px-2 py-1.5 hover:bg-muted hover:no-underline">Accessibility</Link.link>
</div>
</:content>
</Popover.popover>
</nav>
Positioning options
Popover exposes the shared Floating UI options used by Tooltip, Dropdown, and Date Picker: side, offset, strategy, shift, placement_strategy, arrow, and hide.
<div class="flex justify-center">
<Popover.popover
id="positioned-popover"
label="Notification actions"
side={:top_start}
offset={16}
strategy={:fixed}
placement_strategy={:auto_placement}
shift
arrow
hide
>
<:trigger><Button.button kind={:outline}>Notification actions</Button.button></:trigger>
<:content>
<div class="grid gap-2">
<Button.button
size={:sm}
class="w-full"
phx-click="demo-action"
phx-value-action="All notifications marked as read"
>
Mark all as read
</Button.button>
<Button.button
size={:sm}
kind={:outline}
class="w-full"
phx-click="demo-action"
phx-value-action="Opening notification settings"
>
Notification settings
</Button.button>
</div>
</:content>
</Popover.popover>
</div>
Programmatic control
Use Popover.show/1, Popover.hide/1, or
Popover.toggle/1
inside a Phoenix JS
command pipeline to control the same local browser state.
<div class="flex flex-wrap items-center justify-center gap-2">
<Button.button id="open-command-popover" phx-click={Popover.show("command-popover")}>Open popover</Button.button>
<Button.button id="close-command-popover" kind={:outline} phx-click={Popover.hide("command-popover")}>Close popover</Button.button>
<Popover.popover id="command-popover" label="Quick actions" side={:bottom}>
<:trigger><Button.button id="command-popover-trigger" kind={:ghost}>Quick actions</Button.button></:trigger>
<:content>
<div class="grid gap-2">
<Button.button
size={:sm}
kind={:ghost}
class="w-full !justify-start"
phx-click="demo-action"
phx-value-action="Search opened"
>
Open search
</Button.button>
<Button.button
size={:sm}
kind={:ghost}
class="w-full !justify-start"
phx-click="demo-action"
phx-value-action="New task started"
>
Create task
</Button.button>
</div>
</:content>
</Popover.popover>
</div>
Add this module
mix dizzy.add popover
Component API
Popover.popover
Attributes
| Name | Type | Default | Values |
|---|---|---|---|
arrow |
:boolean
| false | — |
class |
:any
| nil | — |
hide |
:boolean
| false | — |
id |
:string
| Required | — |
label |
:string
| Required | — |
offset |
:integer
| 8 | — |
open_on_focus |
:boolean
| false | — |
open_on_hover |
:boolean
| false | — |
placement_strategy |
:atom
| :flip | nil, :flip, :auto_placement |
shift |
:boolean
| true | — |
side |
:atom
| :bottom | :top, :top_start, :top_end, :bottom, :bottom_start, :bottom_end, :left, :left_start, :left_end, :right, :right_start, :right_end |
strategy |
:atom
| :fixed | :absolute, :fixed |
Slots
| Name | Required |
|---|---|
content | Required |
trigger | Required |
JavaScript hook
DizzyPopover
Connects the explicit trigger and content slots, manages click, hover, focus, outside-click, and Escape behavior, and uses the shared Floating UI positioning layer for collision handling, arrows, and detached-reference hiding.
mix dizzy.add includes the hook and performs
any required asset setup automatically.