elements
Demos UI Feed Discuss Install
Style System
  • Style
  • Themes
  • Behaviors
Forms
  • Button
  • ButtonGroup
  • Checkbox
  • ColorPicker
  • Combobox
  • Field
  • FileUpload
  • Input
  • InputGroup
  • InputOTP
  • Label
  • NativeSelect
  • NumberInput
  • PasswordInput
  • RadioGroup
  • Select
  • Slider
  • Switch
  • TagsInput
  • Textarea
  • Toggle
  • ToggleGroup
Overlays
  • AlertDialog
  • Command
  • ContextMenu
  • Dialog
  • Drawer
  • DropdownMenu
  • HoverCard
  • Menubar
  • NavigationMenu
  • Popover
  • Sheet
  • Toast
  • Tooltip
Display
  • Accordion
  • Alert
  • AspectRatio
  • Avatar
  • Badge
  • Breadcrumb
  • Card
  • Collapsible
  • Empty
  • Item
  • Kbd
  • Pagination
  • Progress
  • Separator
  • Skeleton
  • Spinner
  • Table
  • Tabs
  • Tree
Layout
  • Carousel
  • Resizable
  • ScrollArea
  • Sidebar
Dates
  • Calendar
  • DatePicker
  • DateRangePicker
  • TimePicker
Data
  • AreaChart
  • BarChart
  • Chart
  • DataTable
  • Kanban
  • LineChart
  • PieChart
  • RadarChart
  • RadialChart
  • VirtualList
Chat
  • Attachment
  • Message
  • MessageList
  • PromptInput
Elements UI / Overlays / Popover

Popover

Rich content in a floating panel, opened by a trigger, that keeps the page usable.

Basic

A form in a popover. Focus moves to the first field; Escape, a press outside or tabbing out close it.

complete:
Code
<Popover title="Dimensions" description="Set the dimensions for the layer.">
  <Button slot="trigger">Open popover</Button>
  <div class="field">
    <label for="width">Width</label>
    <input id="width" value={layer.width}>
  </div>
  ...
</Popover>

Usage

import { Popover, PopoverClose, PopoverTrigger, PopoverViewport } from "@elements/ui/popover";

<Popover title="Dimensions" description="Set the dimensions for the layer.">
  <Button slot="trigger">Open popover</Button>
  <label>Width <input value={layer.width}></label>
  <label>Height <input value={layer.height}></label>
</Popover>

<!-- controlled: open is two-way -->
<Popover open={state.filters}>...</Popover>

Examples

Header parts

PopoverHeader, PopoverTitle and PopoverDescription take rich content and name the dialog.

Code
<Popover>
  <Button slot="trigger">Open popover</Button>
  <PopoverHeader>
    <PopoverTitle>Title</PopoverTitle>
    <PopoverDescription>Description text here.</PopoverDescription>
  </PopoverHeader>
</Popover>

Label

Without a title, label names the dialog through aria-label.

Code
<Popover label="Text color" size="auto">
  <Button slot="trigger">Text color</Button>
  <Button size="sm">Ink</Button>
  ...
</Popover>

Align

align lines the content up with the trigger's start, center or end.

Code
<Popover align="start">...</Popover>
<Popover align="center">...</Popover>
<Popover align="end">...</Popover>

Side and arrow

side picks where it opens; it flips when there is no room. arrow points at the trigger.

Code
<Popover side="top" arrow size="auto">
  <Button slot="trigger">Top</Button>
  Opens above.
</Popover>

RTL

In a right-to-left region, inline-start opens on the right and inline-end on the left, and align start lines up with the trigger's right edge.

Code
<div dir="rtl">
  <Popover side="inline-start" size="auto">
    <Button slot="trigger">بداية السطر</Button>
    يفتح على اليمين.
  </Popover>
  <Popover side="inline-end" size="auto">...</Popover>
  <Popover align="start" size="sm">...</Popover>
</div>

Anchor

The anchor slot positions the content against another element than the trigger.

Positioned here
Code
<Popover>
  <Button slot="trigger">Open</Button>
  <span slot="anchor" class="anchor">Positioned here</span>
  ...
</Popover>

Controlled

Bind open to state. It writes false back when it closes itself; onOpenChange reports only its own changes. Here Escape is ignored.

open: false changes: escapes: 0
Code
<Button onclick={() => state.open = true}>Open from anywhere</Button>

<Popover open={state.open} onOpenChange={(open) => log(open)}
         onEscapeKeyDown={(e) => e.preventDefault()}>
  <Button slot="trigger">Trigger</Button>
  <PopoverClose><Button>Done</Button></PopoverClose>
</Popover>

Modal

modal traps focus, locks page scroll and blocks the page until it closes.

Code
<Popover modal title="Share">
  <Button slot="trigger">Share</Button>
  ...
</Popover>

Open on hover

openOnHover opens it after delay without taking focus; a click pins it open.

Code
<Popover openOnHover delay={200} closeDelay={150} title="Quick look">
  <Button slot="trigger">Hover or click</Button>
  ...
</Popover>

Nested

A popover opened from a popover. Escape closes the top one first.

Code
<Popover title="Filters">
  <Button slot="trigger">Filters</Button>
  <Popover side="right" title="Advanced">
    <Button slot="trigger">Advanced</Button>
    ...
  </Popover>
</Popover>

Focus on open and close

initialFocus={false} opens without moving focus. initialFocus with a selector focuses that element, such as a static paragraph at the top of long content. finalFocus sends focus to another element on close. returnFocus={false} leaves it alone.

prevented:
Code
<Popover initialFocus={false} title="Stays on the trigger">
  <Button slot="trigger">Open, keep focus</Button>
  ...
</Popover>

<Popover finalFocus="#next-step" title="Rename">
  <Button slot="trigger">Rename</Button>
  <input aria-label="Name">
</Popover>
<Button attrs={{ id: "next-step" }}>Next step</Button>

<Popover returnFocus={false}>...</Popover>

<Popover initialFocus="#release-notes" title="Release notes">
  <Button slot="trigger">What's new</Button>
  <p id="release-notes" tabindex="-1">Version 2 adds shared folders.</p>
  <input aria-label="Feedback">
</Popover>

<Popover onOpenAutoFocus={(e) => e.preventDefault()}
         onCloseAutoFocus={(e) => e.preventDefault()}>...</Popover>

Keep open

dismissOnOutside={false} ignores presses outside. onPointerDownOutside and onFocusOutside can each cancel their close with preventDefault.

outside kept: 0, focus kept: 0 interact kept: 0
Code
<Popover dismissOnOutside={false} title="Stays open">
  <Button slot="trigger">Sticky</Button>
  <PopoverClose><Button>Close</Button></PopoverClose>
</Popover>

<Popover onPointerDownOutside={(e) => e.preventDefault()}
         onFocusOutside={(e) => e.preventDefault()}>
  ...
</Popover>

Hide when detached

hideWhenDetached hides the content while its trigger is scrolled out of view, and shows it again when the trigger returns.

Scroll this box. The popover hides once the button leaves it.

Code
<div class="scroller">
  <Popover hideWhenDetached size="auto">
    <Button slot="trigger">Open, then scroll</Button>
    Hidden while the trigger is out of view.
  </Popover>
</div>

Offsets and collisions

sideOffset and alignOffset move the content off its anchor. collisionPadding sets the room kept from the viewport edge; avoidCollisions={false} keeps it on its side with no flip or shift.

Code
<Popover align="start" sideOffset={12} alignOffset={8}>...</Popover>
<Popover align="end" collisionPadding={24}>...</Popover>
<Popover side="left" avoidCollisions={false}>...</Popover>

Collision avoidance and position method

collisionAvoidance tunes how it stays in view: side "flip", "shift" (keep the side, slide into view) or "none"; align "flip", "shift" or "none"; fallbackAxisSide "end", "start" or "none" for the other axis. positionMethod="absolute" places it in page coordinates instead of fixed ones.

Code
<Popover side="top" collisionAvoidance={{ side: "none", align: "shift" }}>
  <Button slot="trigger">Stays on top</Button>
  Kept above the button even without room, slid along to stay in view.
</Popover>
<Popover align="start" collisionAvoidance={{ align: "flip", fallbackAxisSide: "none" }}>...</Popover>
<Popover side="top" collisionAvoidance={{ side: "shift" }}>...</Popover>
<Popover side="top" collisionAvoidance={{ fallbackAxisSide: "start" }}>...</Popover>
<Popover positionMethod="absolute">...</Popover>

Function offsets

sideOffset and alignOffset also take a function of the anchor's and the content's sizes, the side and the alignment.

Code
<Popover sideOffset={(d) => d.anchor.height} size="auto">
  <Button slot="trigger">One trigger height below</Button>
  ...
</Popover>

Collision boundary, sticky and arrow padding

collisionBoundary keeps the content inside an element as well as the viewport. sticky="always" keeps it in the boundary after its anchor leaves. arrowPadding keeps the arrow off the corners.

Open one, then scroll this box.

Code
<div id="panel">
  <Popover align="start" collisionBoundary="#panel" size="sm">...</Popover>
</div>

<Popover side="right" sticky="always" collisionBoundary="#list">...</Popover>
<Popover arrow align="start" arrowPadding={80}>...</Popover>

Change reasons

onOpenChange gets a second argument: the reason for the change, the event, and cancel() to keep the current state.

reasons:
Code
<!-- details.trigger is the trigger element that caused the change, if one did -->
<Popover onOpenChange={(open, details) => log(open, details.reason, details.trigger)}>
  <Button slot="trigger">Open</Button>
  ...
</Popover>

<!-- details.cancel() keeps the current state -->
<Popover onOpenChange={(open, details) => details.reason === "escape-key" && details.cancel()}>
  ...
</Popover>

Anchor selector

anchor takes a selector for an element anywhere on the page to position against.

All saved
Code
<Popover anchor="#toolbar-status" size="auto">
  <Button slot="trigger">Status</Button>
  Positioned under the status text.
</Popover>
<span id="toolbar-status">All saved</span>

Sizes

size sets the width: auto fits the content, sm, md (the default) and lg are fixed steps.

Code
<Popover size="auto">...</Popover>
<Popover size="sm">...</Popover>
<Popover>...</Popover>            <!-- md -->
<Popover size="lg">...</Popover>

Constrain the content size

A tall popover stays inside the viewport and scrolls; with an arrow the arrow stays visible outside the scroll box.

Code
<Popover arrow size="sm" title="Recent files">
  <Button slot="trigger">Recent files</Button>
  <ul>...twenty files...</ul>
</Popover>

Multiple triggers

PopoverTrigger with a handle opens one popover from anywhere on the page. It sits against the trigger pressed; pressing another moves it there. payload carries that trigger's data, and PopoverViewport cross-fades the content as it changes.

Code
<PopoverTrigger handle="file" payload="report.pdf">
  <Button>report.pdf</Button>
</PopoverTrigger>
<PopoverTrigger handle="file" payload="budget.xlsx">
  <Button>budget.xlsx</Button>
</PopoverTrigger>

<Popover handle="file" payload={state.file} size="auto" title="File">
  <PopoverViewport>Share {state.file} with your team.</PopoverViewport>
</Popover>

Multiple triggers, controlled

Bind open, triggerId and payload to open the popover on a chosen trigger from code; each binding is two-way.

open: false, trigger: , payload:
changes:
Code
<Button onclick={() => { state.trigger = "share-b"; state.open = true; }}>Open on the second</Button>

<PopoverTrigger handle="share" payload="Design"><Button attrs={{ id: "share-a" }}>Design</Button></PopoverTrigger>
<PopoverTrigger handle="share" payload="Engineering"><Button attrs={{ id: "share-b" }}>Engineering</Button></PopoverTrigger>

<Popover handle="share" open={state.open} triggerId={state.trigger} payload={state.team}
         onOpenChange={(open, details) => log(open, details.trigger?.id)}>
  Invite {state.team}.
</Popover>

Trap focus

modal="trap-focus" keeps Tab inside the popover, but the page still scrolls and a press outside still closes it.

Code
<Popover modal="trap-focus" title="Rename">
  <Button slot="trigger">Rename</Button>
  <input aria-label="Name">
  <input aria-label="Description">
</Popover>

Backdrop

backdrop dims the page under the open popover. A press on it closes the popover without reaching the page.

Code
<Popover backdrop title="Delete 3 files?">
  <Button slot="trigger">Delete</Button>
  ...
</Popover>

Disable anchor tracking

disableAnchorTracking places the content once when it opens; it stays where it is while the page scrolls, and is placed again when the window resizes.

Code
<Popover disableAnchorTracking size="auto">
  <Button slot="trigger">Pinned in place</Button>
  Placed once; stays put while the page scrolls.
</Popover>

Keep mounted

keepMounted keeps the closed content in the page, hidden, so a field inside keeps what was typed.

Draft

Code
<Popover keepMounted title="Draft">
  <Button slot="trigger">Draft</Button>
  <input aria-label="Note">   <!-- keeps what was typed while closed -->
</Popover>

Open on load

open={true} without a binding: the popover is open when the page loads, then owns its state.

Code
<Popover open={true} title="Tip">
  <Button slot="trigger">Tip</Button>
  Open when the page loads. The popover owns open from here.
</Popover>

Tooltip on the trigger

A Tooltip around the trigger button labels it. The popover's aria, its position and the focus it returns on close land on the button inside.

Code
<Popover title="Filters" size="sm">
  <Tooltip slot="trigger" content="Filter the list">
    <Button slot="trigger">Filters</Button>
  </Tooltip>
  <label>Name <input></label>
</Popover>
Reference Popover in the manual Every part, attribute, slot, key and data attribute.
Prev NavigationMenuNext Sheet
Product
Manual Pricing Compare
Company
History Contact
Legal
Privacy Policy Acceptable Use Terms License Agreement
© Elements Dev Corporation 2026