--- title: Popover description: Popovers anchor rich content to a trigger element (filters, mini forms, or contextual details) without modal focus trapping. category: Overlays --- import { meta as PopoverMeta, Default } from '@/content/stories/popover'; import Canvas from '@/components/component-docs/canvas'; import Demo from '@/components/component-docs/demo'; export const config = { dependencies: { components: [Canvas, Demo], scripts: [ '../../components/component-docs/demo.script.tsx', '../../components/component-docs/canvas.script.tsx', '../../components/component-docs/controls.script.tsx', ], }, }; # Popover

Popovers anchor rich content to a trigger element (filters, mini forms, or contextual details) without modal focus trapping.

## Try it ## Usage Wrap a child `RuiPopover` in `RuiPopoverTrigger`. Control `open`, `placement`, and `portal` on the trigger or popover. ```tsx import { RuiPopover, RuiPopoverTrigger, RuiPopoverContent } from '@ecopages/radiant-ui/popover'; import { RuiButton } from '@ecopages/radiant-ui/button'; Filter}>

Show items from the last 7 days.

``` ## Custom markup `` coordinates any light-DOM tree that matches its query contract. Pair with `` for click-to-toggle, or pass `anchor` for an external selector. ```tsx import '@ecopages/radiant-ui/popover';
; ``` For a standalone popover anchored elsewhere, omit `rui-popover-trigger` and set `anchor="#my-button"` on ``. ## Listbox variant

Set `variant="listbox"` when the popover hosts a `RuiListbox`; width and focus behavior adapt automatically.

## Match anchor width

Enable `matchAnchorWidth` for select-style popovers so the panel aligns with the trigger.

## Theming The popover surface maps to **semantic surface roles**, never Tailwind palette steps. The panel is often portaled; override `--rui-popover-*` on `.rui-popover`. | Part | Default | Override | | --- | --- | --- | | Fill / border / radius / shadow | `--background`, `--border`, `--radius-container`, `--shadow-overlay` | `--rui-popover-surface`, `--rui-popover-border-color`, `--rui-popover-radius`, `--rui-popover-shadow` | | Listbox variant | Stripped padding for embedded listboxes | class `rui-popover--listbox` | | Z-order | `popover` | theme z-index | ```css .rui-popover { --rui-popover-radius: var(--radius-control); } ``` ## Accessibility - Triggers expose `aria-expanded` when the popover is open. - Content is focusable and dismissible with Escape. - Do not trap focus; users should reach surrounding content without closing first. ## API `RuiPopover` is a custom element (``) positioning a floating `role="dialog"` surface. `RuiPopoverTrigger` (``) coordinates open state with the trigger click. ### Attributes (``) | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `open` | `boolean` | `false` | Whether the popover is open (controlled). | | `placement` | `RuiPlacement` | `bottom` | Placement relative to the anchor. | | `portal` | `boolean` | `true` | Teleport the surface to `document.body`. | | `match-anchor-width` | `boolean` | `false` | Match the anchor width (dropdown menus). | | `offset` | `number` | `8` | Gap between anchor and surface in px. | | `anchor` | `string` | `''` | CSS selector for an external anchor. | | `variant` | `default` · `listbox` | `default` | Surface variant; `listbox` strips padding. | ### Attributes (``) | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `open` | `boolean` | `false` | Whether the popover starts open. | ### Light-DOM contract (``) | Target | Required | Host writes | Author owns | | --- | --- | --- | --- | | `[data-ref="host"]` | yes | — | anchor + surface wrapper | | `[data-ref="surface"]` | yes | `id`, portal position | floating panel content | | `[data-popover-trigger]` | yes* | `aria-controls`, `aria-expanded` on anchor | pressable anchor wrapper | \* Or use the `anchor` attribute with a CSS selector instead of `[data-popover-trigger]`. Nested hosts: `rui-popover-trigger` (parent provides the trigger target). ### Light-DOM contract (``) | Target | Required | Host writes | Author owns | | --- | --- | --- | --- | | `[data-ref="root"]` | yes | — | click delegate root | | `[data-popover-trigger]` | yes | — | pressable anchor | | `rui-popover` | yes | syncs `open` both ways | child popover element | ### Events | Event | Detail | Description | | --- | --- | --- | | `rui-open-change` | `{ open: boolean }` | Emitted when open state changes. | ### Methods (``) | Method | Description | | --- | --- | | `setOpen(next, emit?)` | Toggle open state. Pass `emit = false` to sync without firing `rui-open-change`. | ### View helpers | Component | Target stamped | Notes | | --- | --- | --- | | `RuiPopoverTrigger` | `[data-ref="root"]`, `[data-popover-trigger]` | Wraps child `rui-popover`. Requires `trigger` prop. | | `RuiPopover` | `[data-ref="host"]`, `[data-ref="surface"]`, optional `[data-popover-trigger]` | `trigger` prop for inline anchor; `children` are surface content. | | `RuiPopoverContent` | — | Body inside `[data-ref="surface"]`; not a query target. | ### CSS classes Public BEM classes (documented via `@cssclass`): | Class | Description | | --- | --- | | `.rui-popover-host` | Anchor + surface wrapper. | | `.rui-popover` | Floating surface (`role="dialog"`); `background` + `rounded-container` + `shadow-overlay`. | | `.rui-popover--listbox` | Stripped padding for embedded listboxes. | | `.rui-popover-trigger` | Trigger + popover wrapper. | ### Theme roles | Part | CSS variables consumed | | --- | --- | | Surface | `--background`, `--on-background`, `--border`, `--radius-container`, `--shadow-overlay` | | Z-order | `--z-popover` |