React Popover Component Examples
Copy-paste React Popover examples: share menu, info panel, form field hint, profile menu, and four-side placement. Zero runtime deps. TypeScript only.
A popover is the right affordance whenever a click should open a short floating panel anchored to its trigger — a share menu, an info panel, a form-field hint, a profile dropdown. The Popover component in Drivn renders a <div className="relative inline-flex"> root, a trigger button styled as the Drivn outline button, and a content panel positioned to one of four sides (top, bottom, left, right). The whole tree lives in @/components/ui/popover.tsx, owns its open state through React.useState, and dismisses on outside click through a mousedown listener on document. There is no Radix dependency, no portal, and no focus trap.
This page collects the call-site shapes that ship in real projects. Every example imports the component from @/components/ui/popover — the path the Drivn CLI installs under — and uses the dot-notation API of Popover, Popover.Trigger, and Popover.Content. Every snippet is TypeScript because Drivn ships TypeScript-only.
The examples below cover a default bottom-anchored popover, an info popover positioned to the right of an icon button, a placement-switching example that demonstrates all four sides, a share menu with copy and social actions, and a form-field hint that opens next to a label. The full component reference and props table live on the Popover docs page, and the shadcn/ui comparison sits on Drivn vs shadcn Popover. For a fresh install, see Installation and the Drivn CLI docs.
Default bottom-anchored popover
The shortest possible Popover. Pass children to Popover.Trigger to render the outline button text, then pass content to Popover.Content to render the floating panel. The default position is bottom, so the panel anchors top-full mt-2 left-1/2 -translate-x-1/2 relative to the root — eight pixels below the trigger and horizontally centered on it. The panel ships with min-w-[200px] p-4 bg-card border border-border rounded-[12px] shadow-lg so a short heading plus a sentence of body text fits without extra wrapper styling.
This is the right starting shape for a brief help panel, a confirmation prompt with no input fields, or any short content that fits in a 200-pixel-wide panel without scrolling. The trigger inherits the Drivn outline button styling — rounded corners, muted border, hover background — so it sits next to other outline buttons in a toolbar without visual mismatch. For panels that need a different trigger element (an avatar, an icon-only button, a link), see the form-field hint example later on this page or read Drivn vs shadcn Popover for the trigger-customization story.
1 import { Popover } from "@/components/ui/popover" 2 3 export default function HelpPopover() { 4 return ( 5 <Popover> 6 <Popover.Trigger>What's this?</Popover.Trigger> 7 <Popover.Content> 8 <p className="font-semibold mb-1">Quick help</p> 9 <p className="text-sm text-muted-foreground"> 10 The status badge reflects the latest deploy. Hover for the deploy ID. 11 </p> 12 </Popover.Content> 13 </Popover> 14 ) 15 }
Side-anchored info panel
Set position="right" on the root to anchor the content to the right of the trigger. The panel slides to left-full ml-2 top-1/2 -translate-y-1/2 — eight pixels to the right of the trigger, vertically centered on it. The right placement is the natural fit when the trigger sits at the start of a row and the content needs the horizontal space the row already provides. Use top for triggers at the bottom of the viewport and left when the trigger sits at the right edge of a container.
Drivn's placement is fixed at render time — the panel does not flip to the opposite side when it would collide with the viewport edge. The intentional simplification means triggers placed too close to a viewport edge will render content that overflows the viewport. The fix is to pick the placement at the call site based on where the trigger sits, or to switch to the shadcn/ui flavor for Floating-UI-driven collision flipping. See the placement comparison in Drivn vs shadcn Popover for the full trade-off discussion.
1 import { Popover } from "@/components/ui/popover" 2 3 export default function SideInfoPopover() { 4 return ( 5 <Popover position="right"> 6 <Popover.Trigger>Details</Popover.Trigger> 7 <Popover.Content> 8 <p className="font-semibold mb-1">Build details</p> 9 <p className="text-sm text-muted-foreground"> 10 Last successful build ran for 42 seconds across 14 jobs. 11 </p> 12 </Popover.Content> 13 </Popover> 14 ) 15 }
All four placements side by side
For a layout demo or a settings panel that lets the user pick where a popover opens, render four Popover roots side by side with each position value. The component re-renders cleanly when the position prop changes because the value flows through the context to Popover.Content, which reads styles.positions[position] and applies the matching placement class. Wrap the four triggers in a flex items-center gap-3 row so the buttons sit in a tidy strip and the four popovers open in their distinct directions when clicked.
This shape doubles as a visual-regression test fixture — open each placement in turn and confirm the panel anchors to the expected side of the trigger. The default min-w-[200px] width means the right and left placements occupy a similar horizontal footprint to the top and bottom placements, so the four panels look balanced when compared. Pair the demo with the Popover docs page when teaching the placement model in a design system handbook.
1 import { Popover } from "@/components/ui/popover" 2 3 export default function PlacementDemo() { 4 return ( 5 <div className="flex items-center gap-3"> 6 {(['top', 'bottom', 'left', 'right'] as const).map((side) => ( 7 <Popover key={side} position={side}> 8 <Popover.Trigger>{side}</Popover.Trigger> 9 <Popover.Content> 10 <p className="text-sm">Anchored to {side}.</p> 11 </Popover.Content> 12 </Popover> 13 ))} 14 </div> 15 ) 16 }
Form-field hint popover
A common form pattern is a label with an info icon next to it that opens a popover explaining the field. Place the Popover next to the Input inside the same <label> block, render the trigger as the info icon with aria-label="Field info", and let the content explain validation rules, accepted formats, or examples. The pattern keeps the inline help available without permanently consuming layout space below the input — readers who already know the field skip past the icon, readers who need help click it.
Use position="right" so the content opens to the right of the icon rather than below it, which avoids covering the input on a focused field. For mobile layouts where horizontal space is tighter, switch to position="top" so the panel stacks above the field instead. The popover content can include a short example or a code snippet inside a <code> tag — keep the panel narrow (the default 200-pixel min-width is usually enough) so the inline help reads as a footnote rather than competing with the form layout. Pair the popover with the Drivn Input and Label components for the rest of the field wiring.
1 'use client' 2 import { Popover } from "@/components/ui/popover" 3 import { Input } from "@/components/ui/input" 4 import { Info } from "lucide-react" 5 6 export default function ApiKeyField() { 7 return ( 8 <label className="block"> 9 <div className="flex items-center gap-2 mb-1"> 10 <span className="text-sm font-medium">API key</span> 11 <Popover position="right"> 12 <Popover.Trigger aria-label="Field info" className="p-1"> 13 <Info className="h-4 w-4" /> 14 </Popover.Trigger> 15 <Popover.Content> 16 <p className="text-sm"> 17 Starts with <code>sk_</code> followed by 32 characters. Copy from 18 the dashboard under Settings → API. 19 </p> 20 </Popover.Content> 21 </Popover> 22 </div> 23 <Input placeholder="sk_..." /> 24 </label> 25 ) 26 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
Yes. The component owns open state through React.useState, attaches a mousedown listener to document through React.useEffect, and shares the state through a React.createContext value — all three are client-only APIs. The first line of the registry source is 'use client' for exactly this reason. The call sites that import Popover are free to live in Server Components, since the 'use client' boundary is declared inside the popover file itself rather than at the call site.
The cleanest path is to expose a controlled mode by lifting the open state to the parent. Edit @/components/ui/popover.tsx to accept open and onOpenChange props on PopoverRoot, fall back to the internal useState when the props are undefined, and pass open and setOpen through the context. The parent then owns the boolean and can flip it to false from any handler — for example, after a successful copy-to-clipboard action inside the share menu. The edit is roughly ten lines and follows the same pattern as Drivn Dialog.
Drivn renders the content panel inline rather than through a portal. The panel is positioned with absolute z-50 relative to the root, which means any ancestor with overflow: hidden will clip the panel at its boundary. The two fixes are to move the popover outside the clipping ancestor (typically by lifting it to the same level as the card rather than inside it) or to switch the ancestor to overflow-visible for the duration the popover is open. shadcn/ui's Popover sidesteps the issue through a Radix portal, which is the right call when the popover must escape a card or modal stack.
The default Popover.Trigger renders the Drivn Button component with variant="outline". To use a different element, edit the Trigger function inside @/components/ui/popover.tsx and replace the <Button> JSX with the element you want — a plain <button> for an icon-only trigger, a Drivn Avatar for a profile menu, a <span> with a hover-underline for an inline-help affordance. The usePopover() hook still works inside the new trigger because the context lives at the component file level rather than inside the Button wrapper.
No — the panel is click-driven. Clicking the trigger toggles the open flag, clicking outside the root closes the panel, and the Popover.Content does not respond to pointer-enter or pointer-leave events. For a hover-driven floating panel, use the Drivn Tooltip component instead, which is designed for the short hover-hint case. Mixing click and hover triggers on the same affordance tends to confuse keyboard users, so picking one model per call site is the cleaner pattern.

