Popover
A small panel that opens next to a button and holds interactive content, like a short form, a few settings or links. It doesn't block the page; for a plain label, use a tooltip.
Installation
Set up Camaleo once (Installation), then import what you need. It renders on the client.
import { Popover, PopoverTrigger, PopoverContent, PopoverTitle, PopoverDescription, PopoverClose } from "@camaleo/react";Anatomy
PopoverContent renders in a portal, with the skin, mode and brand of the page around the trigger. It opens below the trigger, aligned to its start (change it with side and align), and flips to the other side when there is no room. The arrow is always there. Your content goes inside popover-scroll, so put layout (a grid, a gap) on your own wrapper or on that slot, not on PopoverContent.
Examples
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above. Each one starts closed: open it, then try Tab, Escape and a click outside.
A short form
Click Rename. initialFocus puts the cursor in the field, Enter saves, and focus goes back to the button while a status message confirms the new name.
Controlled
With open and onOpenChange you know when it closes. Here that marks the notifications as read and announces it.
From an icon button
The icon button needs an aria-label; PopoverTitle names the panel. Screen readers hear that it opens a dialog and whether it is expanded.
With a close button
Phones have no Escape key. A PopoverClose with a visible × gives everyone a way out.
const total = items.reduce((sum, item) => sum + item.price,0,);
Settings that apply at once
The page stays visible and usable behind a popover, so changes can show up right away, with no Apply button.
Keep focus inside
Tab normally leaves the popover and closes it. With modal="trap-focus", Tab cycles inside, so a half-typed entry isn't lost.
In context
These are whole pieces of UI you can copy, in the skin you picked.
| Customer | Date | Total |
|---|---|---|
| Padaria Estrela | 03/14/2026 | $412.00 |
| Café Aurora | 03/13/2026 | $86.50 |
| Mercado Bom Dia | 03/11/2026 | $1,240.00 |
Send the March invoices
12 customers · due 03/31/2026
Guidelines
Menu gives arrow keys, type-to-select and the right roles for Edit, Duplicate and Delete.
Each one is a Tab stop, and screen readers hear a dialog instead of a menu.
Hover or focus shows "Archive" at once, and it's the same text the screen reader says.
If it only explains the button, nobody should have to open and close a panel. Popovers are for content people interact with.
Props
open prop instead.true: user interaction is limited to the popover: document page scroll is locked, and pointer interactions on outside elements are disabled.
- false: user interaction with the rest of the document is allowed.
- 'trap-focus': focus is trapped inside the popover, but document page scroll is not locked and pointer interactions outside of it remain enabled.
On touch devices, a true modal blocks outside taps but leaves the page scrollable unless the popup spans nearly the full viewport width, matching native iOS behavior.
When modal is true, focus trapping is enabled only if <Popover.Close> is rendered
inside <Popover.Popup>. It can be visually hidden with your own CSS if needed, such as
Tailwind's sr-only utility.
When modal is 'trap-focus', render <Popover.Close> inside <Popover.Popup> so touch
screen readers can escape the popup.PopoverContent takes side, align and the popup's props, including initialFocus and finalFocus. PopoverTrigger takes openOnHover and delay for a panel that also opens on hover; keep the click, so it works on touch and with a keyboard.
Styling hooks
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the popup itself.
Attributes
Tokens it reads
Color
Shape & size
Type & motion
This component only
/* a wider panel for the share popover */
.share [data-slot="popover-popup"] {
inline-size: min(26rem, calc(100vw - 2rem));
}Accessibility
- It is never taller than the space on screen: past that, the content scrolls inside and the arrow stays put. With only text inside, the scrolling box becomes a tab stop so keyboard users can scroll it.
- It is a non-modal dialog: the rest of the page stays reachable. A click outside closes it.
- The trigger has aria-haspopup="dialog" and aria-expanded, so screen readers know what opens and whether it's open.
- PopoverTitle gives the dialog its name. Add one even when the panel is small.
- Results of an action inside it go in a status message: the popover closes and takes its text with it.