Components / Overlays
Popover
A small floating panel attached to a button, for options and details that belong to that button: filters, view settings, a quick form. It sits in the browser's top layer, so no parent can clip it.
When to useLink to "When to use"
- A few related controls that would clutter the page if always visible (filters, display options).
- Extra detail about one item, with links or buttons in it.
Use something else when:
- it is a short text hint with no controls: Tooltip
- it is a list of commands: Menu
- the person must finish or cancel before continuing: Dialog
- the content is long or is a whole task: a page or a Sheet
UsageLink to "Usage"
import { Button, Checkbox, Popover, Stack } from "@arshad-shah/cynosure";
<Popover label="Filters" trigger={(props) => <Button {...props}>Filters</Button>}>
<Stack gap="4">
<Checkbox label="Only errors" />
<Button variant="solid" tone="accent">Apply</Button>
</Stack>
</Popover>trigger is a render prop: spread the props it receives onto your Button or IconButton. They carry the ref, the click handler and the ARIA wiring (aria-expanded, aria-controls, aria-haspopup).
LayersLink to "Layers"
Popovers nest, and Escape closes one layer at a time: an inner popover first, then the outer one. A Tooltip shown inside a popover also closes before the popover does.
Focus and widthLink to "Focus and width"
initialFocus says where focus goes on open: the first control (default), the panel itself, or none. With none, focus stays where it was, so a hint can open beside a field while the person keeps typing; Escape and an outside press still close it, and Tab moves into it when the panel follows the field in the document (render the Popover right after its anchor; with a trigger this is automatic).
width="trigger" makes the panel at least as wide as its trigger or anchor element, lined up with it; width="fixed" gives it exactly the size preset however short its content is. The default, content, sizes it to its content up to the preset.
PropsLink to "Props"
| Prop | What it does | Type | Default |
|---|---|---|---|
labelRequired | Accessible name of the panel (a non-modal dialog). | string | |
trigger | Render the trigger: (props) => <Button {...props}>Filters</Button>. Optional with anchor: a popover opened at a point (a canvas click) has no trigger, and is controlled with open. | ((props: PopoverTriggerProps) => ReactNode) | |
anchor | Place it at a point ({ x, y } in viewport pixels, such as where a canvas was clicked) or beside any element or virtual anchor, instead of the trigger. Focus goes back to where it was when it closes. | VirtualAnchor | { x: number; y: number; } | |
open | Controlled open state. | boolean | |
defaultOpen | Open at first, when uncontrolled. | boolean | |
onOpenChange | Called with the new state when the trigger, Escape or an outside click opens or closes it. | ((open: boolean) => void) | |
placement | Preferred side; flips when it does not fit. start/end follow the reading direction. | Placement | |
size | Panel width preset: the most it grows to (or, with width="fixed", its width). | Size | |
width | How wide the panel is. content (default): as wide as its content, up to the size preset. trigger: at least as wide as the trigger or anchor element, lined up with it (a list under a wide button). fixed: exactly the size preset (--cy-popover-width-{sm,md,lg}), however short the content. | "content" | "trigger" | "fixed" | |
initialFocus | Where focus goes on open. first (default): the first control inside, or the panel when it has none. panel: the panel itself, so a screen reader starts at its top. none: focus stays where it is (a field the person is typing in); Escape and outside presses still close it, and Tab from the trigger, or from an anchor the panel follows in the document, moves into it. | "none" | "first" | "panel" | |
modal | Keep Tab inside the panel until it closes. | boolean | |
className | Added to the panel, for layout. | string | |
style | Layout only, applied to the panel. | CSSProperties | |
children | The panel's content. Focus moves to its first focusable element on open (see initialFocus). | ReactNode |
Generated from the type definitions in @arshad-shah/cynosure 0.5.0.
Behaviour and accessibilityLink to "Behaviour and accessibility"
| Input | Result |
|---|---|
| Click, tap, Enter, Space on the trigger | Opens or closes |
| Escape | Closes the topmost layer and returns focus to its trigger |
| Pointer down outside | Closes it |
Tab with modal | Cycles inside the panel |
- Focus: on open, focus moves to the first control inside, or to the panel itself when it has none, so screen readers land in it (
initialFocuschanges this). On close, focus returns to the trigger unless you already moved it somewhere else. - Anchor: an anchor element counts as part of the popover, like the trigger: pressing it is not an outside press.
- Role: the panel is a dialog named by
label. Withmodalit isaria-modaland keeps Tab inside. - Position: the panel prefers
placement, flips to the other side when there is no room, and stays inside the viewport.startandendfollow the reading direction. - Shadow DOM: works inside shadow roots (extension content scripts).
- Motion: a short fade and scale, removed under reduced motion.
ThemingLink to "Theming"
Tokens: --cy-popover-padding, --cy-popover-radius and --cy-popover-width-{sm,md,lg}.
createTheme({ components: { popover: { radius: "10px", widthMd: "360px" } } });RelatedLink to "Related"
These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.