PopoverDesign system
ComponentProduct

Popover

Floating panel anchored to a trigger, for short forms and additional details.

npx context-ds add popover
In the product

Usage

Use it for

Small forms and details anchored to a control.

Avoid

  • Essential content only in a popover

Dependencies: surface, @radix-ui/react-popover

Example

examples/popover.tsx
"use client";import { useState } from "react";import { Button } from "@/components/ui/button";import { Field } from "@/components/ui/field";import { Input } from "@/components/ui/input";import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";export function Product() {  const [name, setName] = useState("Acme");  const [draft, setDraft] = useState(name);  const [open, setOpen] = useState(false);  return (    <div className="grid justify-items-start gap-3">      <p role="status" className="text-body-sm">Workspace: {name}</p>      <Popover open={open} onOpenChange={setOpen}>        <PopoverTrigger asChild><Button variant="outline" onClick={() => setDraft(name)}>Rename</Button></PopoverTrigger>        <PopoverContent><form className="grid gap-3" onSubmit={(event) => { event.preventDefault(); if (!draft.trim()) return; setName(draft.trim()); setOpen(false); }}>          <Field label="Workspace name" htmlFor="ws-name" required><Input id="ws-name" value={draft} onChange={(event) => setDraft(event.target.value)} /></Field>          <Button type="submit" variant="primary" size="sm" disabled={!draft.trim()} className="justify-self-end">Save</Button>        </form></PopoverContent>      </Popover>    </div>  );}

Source

components/ui/popover.tsx
"use client";import * as React from "react";import * as PopoverPrimitive from "@radix-ui/react-popover";import { usePortalContainer } from "@/lib/surface";import { cn } from "@/lib/utils";// The dashboard's floating panel: shell corners, a hairline in the shadow, 6px off its trigger, growing out of the// corner nearest the trigger.function Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>) {  return <PopoverPrimitive.Root data-slot="popover" {...props} />;}function PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {  return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;}function PopoverContent({ className, align = "start", sideOffset = 6, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>) {  return (    <PopoverPrimitive.Portal container={usePortalContainer()}>      <PopoverPrimitive.Content        data-slot="popover-content"        align={align}        sideOffset={sideOffset}        collisionPadding={8}        className={cn("ds-scrollbar z-50 max-h-(--radix-popover-content-available-height) w-72 max-w-[calc(100vw-1rem)] origin-(--radix-popover-content-transform-origin) animate-pop-in overflow-y-auto rounded-shell bg-surface p-3 text-body-sm text-fg shadow-floating outline-none", className)}        {...props}      />    </PopoverPrimitive.Portal>  );}function PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {  return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;}export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };

Reference: context-dashboard c0ddf7a src/components/ui/menu.tsx.