Primitives
Popover
Floating panel with a hard offset shadow.
● LIVE
[ interactive — use the trigger to open ]
"use client";import * as React from "react";import { Button } from "@/registry/okibi/ui/button";import { Input } from "@/registry/okibi/ui/input";import { Label } from "@/registry/okibi/ui/label";import { Popover, PopoverContent, PopoverTrigger,} from "@/registry/okibi/ui/popover";export default function PopoverDemo() { const [open, setOpen] = React.useState(false); return ( <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger asChild> <Button variant="outline-tech">Set Coordinates</Button> </PopoverTrigger> <PopoverContent align="start"> <form className="grid gap-3" onSubmit={(e) => { // Demo only — apply the coordinates and dismiss instead of // triggering a native full-page form submit/reload. e.preventDefault(); setOpen(false); }} > <p className="font-ui text-xs uppercase tracking-[0.12em] text-muted-foreground"> Set Coordinates </p> <div className="grid gap-1.5"> <Label htmlFor="popover-demo-lat">Latitude</Label> <Input id="popover-demo-lat" defaultValue="0x4A2" /> </div> <div className="grid gap-1.5"> <Label htmlFor="popover-demo-lng">Longitude</Label> <Input id="popover-demo-lng" defaultValue="0x0C9" /> </div> <Button type="submit" variant="signal" size="sm"> Lock </Button> </form> </PopoverContent> </Popover> );}npx shadcn@latest add https://okibi.cndr.dev/r/popover.jsonInstallation
Install the theme first, then add the component:
npx shadcn@latest add https://okibi.cndr.dev/r/popover.jsonInstall the required dependencies:
npm install @radix-ui/react-popoverCopy the source from the Code tab above into components/ui/popover.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.
Usage
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
<Popover>
<PopoverTrigger>Open</PopoverTrigger>
<PopoverContent>Settings panel…</PopoverContent>
</Popover>Anatomy
<Popover>
<PopoverTrigger /> {/* anchor that opens the panel */}
<PopoverContent /> {/* portaled HUD panel — name it with a heading */}
</Popover>Examples
Form
Anchor a compact settings form to a trigger — labeled fields on the hard-shadow surface, dismissed on outside click.
● LIVE
[ interactive — use the trigger to open ]
"use client";import { Button } from "@/registry/okibi/ui/button";import { Input } from "@/registry/okibi/ui/input";import { Label } from "@/registry/okibi/ui/label";import { Popover, PopoverContent, PopoverTrigger,} from "@/registry/okibi/ui/popover";export default function PopoverFormDemo() { return ( <Popover> <PopoverTrigger asChild> <Button variant="outline-tech">Calibrate</Button> </PopoverTrigger> <PopoverContent align="start" className="w-72"> <form className="grid gap-3" onSubmit={(e) => { // Demo only — swallow the native submit so the page never reloads. e.preventDefault(); }} > <div className="grid gap-1"> <p className="font-display text-sm uppercase tracking-wide"> Calibrate </p> <p className="text-xs text-muted-foreground"> Trim the signal chain for SEC // 0x4A2. </p> </div> <div className="grid gap-1.5"> <Label htmlFor="popover-form-gain">Gain</Label> <Input id="popover-form-gain" defaultValue="+6.0 dB" /> </div> <div className="grid gap-1.5"> <Label htmlFor="popover-form-offset">Offset</Label> <Input id="popover-form-offset" defaultValue="0x0C9" /> </div> </form> </PopoverContent> </Popover> );}API Reference
Popover
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | – | Controlled open state. |
onOpenChange | (open: boolean) => void | – | Fires when the panel opens or closes. |
defaultOpen | boolean | false | Initial open state (uncontrolled). |
modal | boolean | false | Trap focus and block background interaction while open. |
PopoverContent
| Prop | Type | Default | Description |
|---|---|---|---|
align | "start" | "center" | "end" | "center" | Alignment against the trigger. |
side | "top" | "right" | "bottom" | "left" | "bottom" | Preferred side to open on. |
sideOffset | number | 4 | Gap in pixels between the trigger and the panel. |
Accessibility
- Radix moves focus into the panel on open, traps it while open, and returns focus to the trigger on close.
- Dismisses on
Escapeand on outside click;modalcan be toggled to control background interaction. PopoverContentsetsoutline-none— correct, since focus lands inside the panel, not on the surface itself.- An anonymous panel has no accessible name — give it a heading or an
aria-labelso it's announced.
Guidelines
Do
- Name the panel — lead with a heading or pass an
aria-labeltoPopoverContent. - Override
classNameto size the panel; the default is a fixedw-72. - For long content add
max-h-[var(--radix-popover-content-available-height)] overflow-y-autoso it scrolls instead of overflowing.
Don't
- Don't use a popover for a short hint that only labels its trigger — reach for a
Tooltip. - Don't pack a popover with so much content it needs its own scroll region by default; that's a
DialogorDrawer. - Don't leave the panel unnamed when there's no visible heading.