Components Popover

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Popover

Click-to-open panel with light dismiss. Optional top_layer mode uses the native popover API to escape clipped containers.
Click to open

A click-to-open panel with light dismiss - click away or press Escape and it's gone. The :trigger slot renders inside a real button (style it with trigger_class); the body is yours. Unlike a tooltip, the content can be interacted with.

heex
<.popover id="showcase-popover" trigger_class="pc-button pc-button--gray-outline pc-button--md">
<:trigger>Open popover</:trigger>
<div class="max-w-56">
<div class="text-sm font-semibold">Dimensions</div>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Set the width and height for the layer.
</p>
</div>
</.popover>
Escaping clipped containers

top_layer paints the panel on the browser's native top layer (the popover API), so an overflow: hidden ancestor - table cells, scroll areas, cards - can't cut it off.

overflow: hidden container
heex
<div class="relative h-24 w-full max-w-md p-4 overflow-hidden border border-dashed rounded-lg border-gray-300 dark:border-gray-700">
<div class="mb-2 text-xs text-gray-400">overflow: hidden container</div>
<.popover
id="showcase-popover-clipped"
placement="bottom"
top_layer
trigger_class="pc-button pc-button--gray-outline pc-button--sm"
>
<:trigger>Open from inside</:trigger>
<div class="max-w-64 text-sm">
This panel paints on the browser top layer - the clipped
container can't cut it off.
</div>
</.popover>
</div>
Properties
Attribute Type Default Description
class any nil extra classes for the wrapper
id string the panel id; autogenerated if not set
on_close {:struct, Phoenix.LiveView.JS} %Phoenix.LiveView.JS{ops: []} additional JS commands to run when the popover closes (default mode only)
panel_class any nil extra classes for the panel
placement string "bottom" which side of the trigger the panel appears on, with optional -start/-end alignment
one of: "top", "top-start", "top-end", "bottom", "bottom-start", "bottom-end", "left", "left-start", "left-end", "right", "right-start", "right-end"
rest global
top_layer boolean false render the panel in the browser top layer so it escapes overflow-clipped containers
trigger_class any nil extra classes for the trigger button
:inner_block slot slot the panel content
:trigger slot slot the trigger content, rendered inside a button