Popover
A panel anchored to whatever opens it
Popover
A panel anchored to whatever opens it, holding anything you like.
Installation
import { Popover, PopoverTrigger, PopoverContent } from '@geckoui/geckoui';Basic Usage
<Popover>
<PopoverTrigger>
<Button>Open</Button>
</PopoverTrigger>
<PopoverContent>
<p>Anything can live in here.</p>
</PopoverContent>
</Popover>PopoverTrigger uses its child as the trigger rather than wrapping it, so your button keeps
its own tag, styling and click handler.
Props API
Popover
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Whether it is showing. Leave it out to let the component hold the state |
defaultOpen | boolean | false | Whether it starts open when uncontrolled |
onOpenChange | (open: boolean) => void | - | Called whenever it opens or closes |
placement | Placement | 'bottom-start' | Where the panel sits. It flips when there is no room |
offset | number | 6 | The gap between the trigger and the nearest part of the popover, in pixels |
floatingStrategy | Strategy | - | Positioning strategy, for the rare case absolute does not work |
dismissOnEscape | boolean | true | Close on Escape |
dismissOnOutsideClick | boolean | true | Close when something outside is clicked |
arrow | boolean | false | Show a small arrow pointing at the trigger |
disabled | boolean | false | Stop it opening at all |
| ...rest | HTMLAttributes<HTMLDivElement> | - | Spread onto the container |
PopoverTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | One element, used as the trigger |
PopoverContent
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | What the panel shows |
| ...rest | HTMLAttributes<HTMLDivElement> | - | Spread onto the panel |
Examples
Placement
<Popover placement="top" arrow>
<PopoverTrigger>
<Button variant="outlined">top</Button>
</PopoverTrigger>
<PopoverContent>
<p>Above the trigger.</p>
</PopoverContent>
</Popover>placement takes any floating-ui placement — top, bottom-end, right-start and so on.
The panel flips to the opposite side when there is no room, and shifts sideways to stay on
screen.
offset is the gap between the trigger and the nearest part of the popover. With arrow
on, that is measured to the arrow's tip rather than the panel edge, so the gap looks the
same whether or not there is an arrow.
A form inside
import { Popover, PopoverTrigger, PopoverContent, usePopover } from '@geckoui/geckoui';
function FilterForm() {
const { close } = usePopover();
return (
<form
onSubmit={(e) => {
e.preventDefault();
close();
}}
>
<Label htmlFor="min">Minimum</Label>
<Input id="min" placeholder="0" />
<Button type="button" variant="ghost" onClick={close}>Cancel</Button>
<Button type="submit">Apply</Button>
</form>
);
}
<Popover>
<PopoverTrigger>
<Button>Filters</Button>
</PopoverTrigger>
<PopoverContent>
<FilterForm />
</PopoverContent>
</Popover>usePopover gives anything inside the popover its state and a close, so a Cancel button
or a form submit can shut it.
Escape closes the popover even from inside a text field, which is the one place a popover
differs from Dialog and Drawer — they leave Escape alone while you are typing.
Controlled
const [open, setOpen] = useState(false);
<Button onClick={() => setOpen((prev) => !prev)}>Toggle from outside</Button>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger>
<Button>The trigger</Button>
</PopoverTrigger>
<PopoverContent>
<p>Driven from outside.</p>
</PopoverContent>
</Popover>Focus
Focus moves into the panel when it opens — onto the first thing focusable inside, or the panel itself when there is nothing — and returns to the trigger when it closes.
Keyboard
| Key | Does |
|---|---|
| Enter Space | Open or close, from the trigger |
| Escape | Close and hand focus back to the trigger |
| Tab | Move through whatever is inside the panel |
Accessibility
- The trigger gets
aria-expandedandaria-haspopup="dialog" - The panel is a
dialog - Focus moves in on open and back to the trigger on close
Styling with CSS
.GeckoUIPopover [the container, which wraps the trigger]
.GeckoUIPopover__content [data-state="open|closed"]
.GeckoUIPopover__arrowThe trigger is your own element, so style it however you already do.
.GeckoUIPopover {
--gecko-popover-bg: var(--color-surface-primary);
--gecko-popover-border: var(--color-border-secondary);
--gecko-popover-radius: 0.375rem;
--gecko-popover-padding: 0.75rem;
--gecko-popover-width: max-content;
--gecko-popover-max-width: 20rem;
--gecko-popover-z: 50;
}The panel sizes itself to its content up to --gecko-popover-max-width. Set
--gecko-popover-width to pin it to a fixed width instead.
Popover, Tooltip or Menu?
- Tooltip — a short label on hover. Not focusable, not interactive
- Popover — a panel you click open, holding anything, including a form
- Menu — a list of actions, with the arrow keys moving between them