// preview in
saved for every page

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.

Built on Base UIPattern WAI-ARIA Dialog (non-modal)Client componentSource ↗
minimal · light · #3fa34d
modal

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.

minimal · light · #3fa34d
q3-report.pdf

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.

Monthly churn2.4%

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.

Q3 report
Share a documentPopover · Switch · Field · Input · Button
CustomerDateTotal
Padaria Estrela03/14/2026$412.00
Café Aurora03/13/2026$86.50
Mercado Bom Dia03/11/2026$1,240.00
Table column settingsPopover · CheckboxGroup · Table

Send the March invoices

12 customers · due 03/31/2026

Snooze a reminderCard · Popover · RadioGroup · Badge

Guidelines

DoA list of commands is a menu.

Menu gives arrow keys, type-to-select and the right roles for Edit, Duplicate and Delete.

Don’tA popover full of buttons.

Each one is a Tab stop, and screen readers hear a dialog instead of a menu.

DoA tooltip for a name.

Hover or focus shows "Archive" at once, and it's the same text the screen reader says.

Don’tA click to read a label.

If it only explains the button, nobody should have to open and close a panel. Popovers are for content people interact with.

Props

PropTypeDefaultDescription
defaultOpenbooleanfalseWhether the popover is initially open. To render a controlled popover, use the open prop instead.
openboolean—Whether the popover is currently open.
onOpenChange(open, eventDetails) => void—Event handler called when the popover is opened or closed.
modal"trap-focus" | booleanfalseDetermines if the popover enters a modal state when open. - 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

data-sidebottom · top · left · right
data-alignstart · center · end
data-open · data-closedon the popup
data-starting-style · data-ending-stylewhile it fades in and out

Tokens it reads

Color

--cml-border--cml-fg--cml-fg-muted--cml-focus-offset--cml-focus-ring--cml-focus-width--cml-popover-bg--cml-popover-border--cml-shadow-popover--cml-surface-raised

Shape & size

--cml-border-width--cml-popover-border-width--cml-radius-control--cml-radius-popover

Type & motion

--cml-duration-fast--cml-ease-standard--cml-font-display--cml-font-weight-display--cml-motion-scale

This component only

--cml-corner-shape--cml-item-highlight
/* a wider panel for the share popover */
.share [data-slot="popover-popup"] {
  inline-size: min(26rem, calc(100vw - 2rem));
}

Accessibility

EnterOn the trigger, opens the popover and moves focus to its first control, to initialFocus, or to the panel when it has no controls.
TabMoves through the controls. After the last one, focus goes to what follows the trigger and the popover closes.
Shift + TabFrom the first control, goes back to the trigger; the popover stays open.
EscapeCloses the popover and returns focus to the trigger.
Was this page helpful? · Edit this page on GitHub