Popover
A focusable, non-modal surface anchored to a trigger with logical placement and viewport collision handling.
Installation
import { Popover, PopoverTrigger, PopoverContent } from "madagent";Default
The trigger can compose a design-system Button without adding another DOM element. Focus moves into the panel when it opens and returns to the trigger when it closes.
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open details</Button>
</PopoverTrigger>
<PopoverContent aria-label="Record details">
Contextual content
</PopoverContent>
</Popover>Mini-form
Popover content is non-modal and can hold regular focusable controls for compact editing and filtering workflows.
<Popover>
<PopoverTrigger asChild><Button>Filters</Button></PopoverTrigger>
<PopoverContent aria-label="Filter records">
<Select label="Status" options={statusOptions} />
<Switch label="Include archived" />
<Button size="sm">Apply filters</Button>
</PopoverContent>
</Popover>Placement and alignment
Placement names follow logical axes. The shared positioning hook flips panels at viewport edges and clamps their inline position in both LTR and RTL.
Block placement
<PopoverContent placement="block-start" align="start">
Content
</PopoverContent>Logical inline placement
<PopoverContent placement="inline-end" align="center">
Content
</PopoverContent>Controlled state
Use open and onOpenChange when application state decides whether the panel is visible.
const [open, setOpen] = useState(false);
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild><Button>Details</Button></PopoverTrigger>
<PopoverContent>Controlled content</PopoverContent>
</Popover>Overflow content
Content is capped at nine items and becomes scrollable so the panel remains compact.
<PopoverContent aria-label="Recent activity">
{items.slice(0, 9).map((item) => <button key={item.id}>{item.label}</button>)}
</PopoverContent>Popover Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Controlled open state |
defaultOpen | boolean | false | Initial open state for uncontrolled usage |
onOpenChange | (open: boolean) => void | - | Called whenever the requested open state changes |
closeOnOutsideClick | boolean | true | Closes the panel when pointer interaction starts outside it |
children* | ReactNode | - | PopoverTrigger and PopoverContent composition |
PopoverTrigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Clones one interactive child instead of rendering a button wrapper |
children* | ReactNode | - | Trigger label or one interactive child when asChild is enabled |
PopoverContent Props
| Prop | Type | Default | Description |
|---|---|---|---|
placement | "block-end""block-start""inline-end""inline-start" | "block-end" | Preferred logical side; collision handling can flip it |
align | "start""center""end" | "start" | Logical alignment relative to the trigger |
gap | number | 8 | Distance from the trigger in pixels |
collisionPadding | number | 8 | Minimum viewport-edge padding in pixels |
children* | ReactNode | - | Focusable or presentational panel content |
RTL Support
Components automatically adapt to right-to-left languages using CSS logical properties.
Preview
Usage
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">خيارات التقرير</Button>
</PopoverTrigger>
<PopoverContent align="start" aria-label="خيارات التقرير">
<Input label="اسم التقرير" />
<Button size="sm">تطبيق</Button>
</PopoverContent>
</Popover>