Popover
A small anchored layer for secondary choices and short supporting content.
- Family
- Choice
- Available import
@/components/ui/popover- Status
- Thorbis wrapper and live specimen ship here.
popover24 Example Street. Choose a technician for this local preview. Nothing is saved or sent.
This example needs JavaScript. If the button stays disabled, reload the page.
Preview technician: Maya Chen. Nothing is saved or sent.
- Use
- Use when the task is brief, contextual, and can close without losing work.
- Avoid
- Do not put a long workflow, essential information, or a second popover inside it.
Use this component
GitHub source links require access to the private Thorbis design repository.
Wrapper sourceRepository and dependencies
- Value exports
PopoverPopoverAnchorPopoverContentPopoverTrigger
- Type exports
- None.
Setup, styles, and dependencies
These are React wrappers in the Thorbis design repository, not an installable npm UI package. In this repository, @/ resolves to src/ through tsconfig.json.
Use the repository's package.json and package-lock.json for dependencies. Load src/styles.css once at the application root for global tokens, typography, and themes. The wrappers compose colocated StyleX owners; include the generated token CSS and extracted StyleX output.
For another project, bring the wrapper and its transitive local imports, configure the alias, install the dependencies those files use, and provide the same token and style setup. Review the source before copying; the direct imports below are not a complete dependency closure.
Interactive examples require client-side React. This TanStack Start site server-renders and hydrates React; another host must hydrate or render the component on the client. Example code is the same TypeScript source used by its live preview.
Direct imports in this wrapper:
./overlay-activation./popover.stylex@radix-ui/react-popover@stylexjs/stylexreact
Local technician choice and dismissal
Choose an available sample technician to update a local preview, or dismiss to keep the current choice. Nothing is saved or sent. PopoverContent includes its portal; give its dialog role a purpose through aria-labelledby or aria-label and connect help with aria-describedby when needed. PopoverTrigger asChild keeps a single supplied control and its props/ref. Use open and onOpenChange for controlled state, or defaultOpen for an initial uncontrolled opening. Keyboard, assistive and programmatic opening are instant; accepted pointer activation uses the existing motion token and respects reduced motion. Content fits the available viewport and scrolls when necessary. Radix owns dismissal and focus return. The example's trigger stays disabled with guidance until hydration, and unavailable technicians cannot be selected. The wrappers are repository source; include their transitive local imports and shared styles.
24 Example Street. Choose a technician for this local preview. Nothing is saved or sent.
This example needs JavaScript. If the button stays disabled, reload the page.
Preview technician: Maya Chen. Nothing is saved or sent.
Read the complete example
import { useEffect, useId, useState } from "react";
import { Button } from "@/components/ui/button";
import {
FormHelp,
FormSection,
FormSectionDescription,
FormSectionFields,
FormSectionInfo,
FormSectionTitle,
} from "@/components/ui/form";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
const technicians = [
{ id: "maya", name: "Maya Chen", available: true },
{ id: "luis", name: "Luis Ortega", available: true },
{ id: "casey", name: "Casey Reed", available: false },
] as const;
export function JobAssigneeExample() {
const titleId = useId();
const helpId = useId();
const [ready, setReady] = useState(false);
const [open, setOpen] = useState(false);
const [current, setCurrent] = useState("maya");
useEffect(() => setReady(true), []);
const selected = technicians.find((technician) => technician.id === current)!;
return (
<Popover open={open} onOpenChange={setOpen}>
<FormSection data-example="job-assignee" aria-busy={!ready}>
<FormSectionInfo>
<FormSectionDescription>
24 Example Street. Choose a technician for this local preview. Nothing is saved or sent.
</FormSectionDescription>
{!ready && (
<FormHelp>
This example needs JavaScript. If the button stays disabled, reload the page.
</FormHelp>
)}
</FormSectionInfo>
<FormSectionFields>
<PopoverTrigger asChild>
<Button type="button" variant="chip" disabled={!ready}>
Choose sample technician
</Button>
</PopoverTrigger>
<FormHelp role="status">
Preview technician: {selected.name}. Nothing is saved or sent.
</FormHelp>
</FormSectionFields>
</FormSection>
<PopoverContent aria-labelledby={titleId} aria-describedby={helpId}>
<FormSectionFields>
<FormSectionTitle id={titleId}>Choose sample technician</FormSectionTitle>
<FormHelp id={helpId}>
Choose an available technician. Dismissing keeps the current preview.
</FormHelp>
<FormSectionFields role="group" aria-label="Sample technicians">
{technicians.map((technician) => (
<Button
key={technician.id}
type="button"
variant="ghost"
aria-pressed={current === technician.id}
disabled={!ready || !technician.available}
onClick={() => {
setCurrent(technician.id);
setOpen(false);
}}
>
{technician.name}
{!technician.available ? " (unavailable)" : ""}
</Button>
))}
</FormSectionFields>
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
Keep current technician
</Button>
</FormSectionFields>
</PopoverContent>
</Popover>
);
}
Adoption contract
- Start from the ShadCN accessible source primitive; keep upstream keyboard and focus behavior.
- Use Thorbis color, type, space, radius, shadow, and motion tokens only.
- Design hover, focus, selected, disabled, loading, empty, error, offline, and reduced-motion states where they apply.
- Verify desktop and 390px mobile, keyboard order, accessible names, and stable async geometry.