import {
Button,
Heading,
Popover,
PopoverContent,
PopoverTrigger,
Text,
} from '@vega-ui/react';
<Popover role='dialog'>
<PopoverTrigger asChild><Button variant='secondary' appearance='ghost'>Storage · 82% used</Button></PopoverTrigger>
<PopoverContent aria-labelledby='preview-popover-title'>- Browser support
- Newly available 2025
- Chrome 116
- Edge 116
- Firefox 125
- Safari 17
- Safari on iOS 18.3
- Bundle size
- Popover
- Oklab and OkLCh — comes from the design tokens
- @starting-style — only the open and close animation depends on it
- transition-behavior — only the open and close animation depends on it
- Resize observer
- Intersection observer
- Visual viewport API
- Web animations
- Individual transform properties
- :where()
Summary
Popover is a compound component for non-modal floating content anchored to a trigger. PopoverContent is a native popover="auto" element opened from the trigger through popovertarget, so the browser shows it in the top layer, closes it on an outside click or Escape, and it is never clipped by overflow or covered by a modal dialog. It sits between tooltip-level hinting and full modal overlays.
Imports
import {
Popover,
PopoverTrigger,
PopoverContent,
type PopoverProps,
} from '@vega-ui/react';Exported Types
PopoverPropsPopoverTriggerPropsPopoverContentProps
Minimal Composition
import { useId } from 'react';
import {
Button,
Heading,
Popover,
PopoverContent,
PopoverTrigger,
Text,
} from '@vega-ui/react';
export default function MinimalComposition() {
const titleId = useId();Required Parts
Popover: root state and context providerPopoverTrigger: trigger surfacePopoverContent: floating content
Optional Parts
None. For a dimmed backdrop, use Dialog or Sheet instead.
Composition Order
PopoverPopoverTriggerPopoverContent
Variants
- Trigger-driven or controlled open state
- Lightweight informational popover versus richer custom content
Edge Cases
- Use
DialogorSheetinstead when interaction should be modal or needs a dimmed backdrop. - Floating content must be tested inside scroll containers and overlays.
- Trigger content should clearly indicate whether the popover is open.
- If the content starts accumulating form sections, confirmations, or large action groups, the correct abstraction is usually no longer a popover.
Common Mistakes
- Putting critical task flow inside a popover.
- Forgetting to test placement inside clipped or scrolling containers.
- Letting popover content grow into a mini-dialog with too much text or too many actions.
Examples
Basic
Basic: account menu
role='dialog' on the root gives the content role="dialog" and the trigger aria-haspopup="dialog", and aria-label names the content. Layout styles go on an inner element: a display set on PopoverContent itself would override the browser's hiding of a closed popover.
import {
Button,
Popover,
PopoverContent,
PopoverTrigger,
Separator,
Text,
} from '@vega-ui/react';
export default function AnchoredUserMenu() {
return (
<Popover role='dialog' placement='bottom-end'>Controlled/Stateful
Controlled/Stateful: notifications that mark themselves read
onChangeOpen reports every open and close, from the trigger, an outside click or Escape. Opening clears the unread count, and the trigger's aria-label carries the count for screen readers.
import { useId, useState } from 'react';
import {
Badge,
Heading,
Icon,
IconButton,
Popover,
PopoverContent,
PopoverTrigger,
Text,
} from '@vega-ui/react';
import { BellIcon } from '@vega-ui/icons';Controlled/Stateful: menu that closes on select
A popover does not close by itself when a button inside is pressed. Control open and close it from the item handler, which also records the chosen action.
import { useState } from 'react';
import {
Button,
Popover,
PopoverContent,
PopoverTrigger,
Text,
} from '@vega-ui/react';
const actions = ['Duplicate', 'Move to folder', 'Download as PDF', 'Archive'];
export default function MenuClosesOnSelect() {Controlled/Stateful: lightweight delete confirmation
Each comment has its own controlled popover with Keep and Delete. autoFocus puts focus on the safe answer when the popover opens, and the content is named by its question through aria-labelledby.
import { useId, useState, type CSSProperties } from 'react';
import {
Button,
Popover,
PopoverContent,
PopoverTrigger,
Text,
} from '@vega-ui/react';
const destructive = {
'--button-background-color': 'var(--color-red-accent-500)',
'--button-background-color-hover': 'var(--color-red-accent-600)',Form/Integration
Form/Integration: inline rename form
The form validates natively (required, pattern, maxLength), reads the new name with FormData and closes the popover by setting open to false. The round close button is a plain IconButton that sets open to false, since there is no close part for popovers.
import { useId, useState, type CSSProperties, type FormEvent } from 'react';
import {
Button,
Heading,
Icon,
IconButton,
Label,
Popover,
PopoverContent,
PopoverTrigger,
TextField,
TextFieldInput,Layout/Overlay
Layout/Overlay: popovers inside a scrolling list
Each row has its own popover. The content renders in the top layer, so the list's overflow: auto never clips it, and it follows its trigger while the list scrolls and flips when there is no room below.
import {
Icon,
IconButton,
Popover,
PopoverContent,
PopoverTrigger,
Text,
} from '@vega-ui/react';
import { InfoIcon } from '@vega-ui/icons';
const names = ['Maria Chen', 'Tom Okafor', 'Lea Novak', 'Sam Rivera', 'Ana Silva'];
Error
Error: invalid link address
The form uses noValidate and checks the address on submit: an invalid one keeps the popover open and shows an error linked with aria-describedby and aria-invalid, a valid one is inserted and closes it. Submit the prefilled value to see the error.
import { useId, useState, type FormEvent } from 'react';
import {
Button,
HelperText,
Icon,
IconButton,
Label,
Popover,
PopoverContent,
PopoverTrigger,
Text,
TextField,Disabled
Disabled: trigger disabled until something is selected
The Bulk actions trigger is disabled while no file is selected, and the hint is linked with aria-describedby while it is. A disabled button cannot open its popover through popovertarget.
import { useId, useState } from 'react';
import {
Button,
Checkbox,
CheckboxCheckedIcon,
CheckboxIndeterminateIcon,
CheckboxIndicator,
CheckboxInput,
HelperText,
Popover,
PopoverContent,
PopoverTrigger,Edge
Edge: popover inside a popover
The label picker opens from inside the filters popover. Nested popover="auto" elements stack natively: choosing a label closes only the inner one, and an outside click closes both.
import { useState } from 'react';
import {
Button,
Popover,
PopoverContent,
PopoverTrigger,
Text,
} from '@vega-ui/react';
const labels = ['Bug', 'Feature', 'Design', 'Docs'];
export default function NestedPopover() {Edge: long unbreakable content
PopoverContent is width: max-content, so a long URL would make it as wide as the URL. A max-width on the inner element and overflow-wrap: anywhere keep it within the screen.
import {
Button,
Popover,
PopoverContent,
PopoverTrigger,
Text,
} from '@vega-ui/react';
const webhook = 'https://hooks.northwind.dev/integrations/v2/workspaces/8f3a9c1e-42b7-4d0a-9e61-b5c7d2f40a18/events?signature=sha256';
export default function LongContent() {
return (Anatomy
Overview
Popover owns open state and floating-layer coordination between the trigger and content. It stays non-modal by default, so the composition contract is about anchoring and contextual content rather than isolation.
Required Parts
Popover
Required. Owns open state and anchor coordination.
PopoverTrigger
Required for trigger-driven usage. Toggles the popover through the native popovertarget, so it must be a button; with asChild, the child must render a <button>.
PopoverContent
Required. Renders the floating content surface as a native popover="auto" element in the top layer, so no portal part is needed. It is always rendered and hidden while closed; its children render only while it is open.
Optional Parts
None. For a dimmed backdrop, use Dialog or Sheet instead.
Composition Order
PopoverPopoverTriggerPopoverContent
Valid Composition Patterns
import { useId } from 'react';
import {
Button,
Heading,
Popover,
PopoverContent,
PopoverTrigger,
Text,
} from '@vega-ui/react';
export default function MinimalComposition() {
const titleId = useId();Invalid Composition Patterns
Critical modal task inside PopoverContent
If the flow blocks progress or requires strong focus trapping, use Dialog, Drawer, or Sheet instead.
Large page-like layout inside the popover
This weakens the contextual nature of the component and usually indicates the wrong overlay pattern.
Trigger and content split across unrelated state owners
That makes open state and placement harder to reason about than a single popover tree.