Controlled floating panel anchored to a trigger, with focus return, dismiss-on-escape, and outside-click handling.
Controlled open state for predictable behaviour: open + onOpenChange (React), v-model:open (Vue), [(open)] (Angular)
Floating-UI placement with side, align, and sideOffset
closeOnEscape and closeOnOutsideClick dismissal
Portal-rendered content with surface-aware theming
Compound API: Trigger, Content, Arrow
Status
stable
Category
overlay-foundation
Since
v1.8.0
API
import { PixelPopover } from '@pxlkit/ui-kit';
PixelPopover
Prop
Type
Default
Description
open* (required)
boolean
—
Whether the popover is open; set it from onOpenChange.
onOpenChange* (required)
(open: boolean) => void
—
Called with the open state the trigger, Escape or a press outside asks for.
children* (required)
React.ReactNode
—
PixelPopover.Trigger and PixelPopover.Content.
side
'top' | 'bottom' | 'left' | 'right'
'bottom'
Side of the trigger the content opens on; flips when there is no room.
align
'start' | 'center' | 'end'
'center'
Alignment of the content along that side.
sideOffset
number
8
Gap between trigger and content, in px.
closeOnEscape
boolean
true
Close when Escape is pressed.
closeOnOutsideClick
boolean
true
Close on a press outside the trigger and the content.
surface
'pixel' | 'linear'
—
Surface override; defaults to the nearest provider.
haspopup
'dialog' | 'listbox' | 'menu' | 'tree' | 'grid'
'dialog'
ARIA aria-haspopup value advertised on the trigger. Default 'dialog'. Set to 'listbox' for combobox patterns, 'menu' for menu patterns, etc.
role
'dialog' | 'none' | 'listbox' | 'menu'
'dialog'
Role applied to the content element. Default 'dialog'. Set to 'none' (or any non-dialog value) when the popover wraps an inner widget that owns the semantics (e.g. a listbox inside a combobox).
PixelPopover.Trigger
Prop
Type
Default
Description
children* (required)
React.ReactElement
—
The trigger: one element, which toggles the popover and gets its aria-expanded, aria-haspopup and aria-controls.
ref points to its element (HTMLElement).
PixelPopover.Content
Prop
Type
Default
Description
surface
'pixel' | 'linear'
—
Surface override; defaults to the popover's.
Also takes the native attributes and event handlers of <div> (HTMLAttributes<HTMLDivElement>).
ref points to <div>.
PixelPopover.Arrow
No props of its own.
Also takes the native attributes and event handlers of <span> (HTMLAttributes<HTMLSpanElement>).
ref points to <span>.
Accessibility
WCAG target: 2.1 AA
dialog
Content renders with role="dialog" by default; pair with aria-labelledby on Content. Set role="none" when an inner widget owns semantics. While the content is open, the trigger points at it with aria-controls (Content keeps the id it is given, or gets a generated one); a trigger that sets its own aria-controls keeps it. When the content closes while it holds focus, focus returns to the trigger; after a press outside, focus follows the pointer instead.
Keyboard
Key
Action
When
Escape
Closes the popover when closeOnEscape is true and returns focus to the trigger
—
Usage
import { useState } from 'react';
import { PixelPopover } from '@pxlkit/ui-kit';
export function Default() {
const [open, setOpen] = useState(false);
return (
<PixelPopover open={open} onOpenChange={setOpen}>
<PixelPopover.Trigger>
<button type="button">Open popover</button>
</PixelPopover.Trigger>
<PixelPopover.Content aria-labelledby="popover-title">
<h3 id="popover-title" className="font-bold mb-1">
Popover
</h3>
<p className="text-sm">Floating content anchored to the trigger.</p>
</PixelPopover.Content>
</PixelPopover>
);
}
Examples
Default
import { useState } from 'react';
import { PixelPopover } from '@pxlkit/ui-kit';
export function Default() {
const [open, setOpen] = useState(false);
return (
<PixelPopover open={open} onOpenChange={setOpen}>
<PixelPopover.Trigger>
<button type="button">Open popover</button>
</PixelPopover.Trigger>
<PixelPopover.Content aria-labelledby="popover-title">
<h3 id="popover-title" className="font-bold mb-1">
Popover
</h3>
<p className="text-sm">Floating content anchored to the trigger.</p>
</PixelPopover.Content>
</PixelPopover>
);
}