Siteplane UI

Popover

Open contextual content next to a trigger.

Markdown
"use client";

import { Button } from "@/siteplane/components/button";
import { Field, FieldLabel } from "@/siteplane/components/field";
import { Form } from "@/siteplane/components/form";
import {
  Popover,
  PopoverDescription,
  PopoverPopup,
  PopoverTitle,
  PopoverTrigger,
} from "@/siteplane/components/popover";
import { Textarea } from "@/siteplane/components/textarea";
import { useState } from "react";

export default function Example() {
  const [open, setOpen] = useState(false);
  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger render={<Button variant="outline" />}>
        Give feedback
      </PopoverTrigger>
      <PopoverPopup className="w-80">
        <PopoverTitle>Send feedback</PopoverTitle>
        <PopoverDescription>
          Tell us what would make this page more useful.
        </PopoverDescription>
        <Form
          className="mt-4 grid gap-4"
          onSubmit={(event) => {
            event.preventDefault();
            setOpen(false);
          }}
        >
          <Field>
            <FieldLabel>Feedback</FieldLabel>
            <Textarea
              name="feedback"
              placeholder="What could be better?"
              required
            />
          </Field>
          <Button type="submit">Send feedback</Button>
        </Form>
      </PopoverPopup>
    </Popover>
  );
}

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/popover

Complete the one-time setup first. Registry JSON lists source files and dependencies.

For this page’s examples, also install:

pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/form @siteplane/input @siteplane/input-group @siteplane/textarea

Field is installed with Input.

Usage

import { Button } from "@/siteplane/components/button";
import {
  Popover,
  PopoverTrigger,
  PopoverPopup,
  PopoverTitle,
  PopoverDescription,
} from "@/siteplane/components/popover";

<Popover>
  <PopoverTrigger render={<Button variant="outline" />}>Status</PopoverTrigger>
  <PopoverPopup>
    <PopoverTitle>Project status</PopoverTitle>
    <PopoverDescription>On track for the October release.</PopoverDescription>
  </PopoverPopup>
</Popover>;

Composition

Popover
├── PopoverTrigger
└── PopoverPopup / PopoverContent
    ├── PopoverTitle
    ├── PopoverDescription
    └── PopoverClose

Close button

Set showCloseButton on PopoverPopup for a close button in the top-end corner, the same one Dialog, Sheet and Drawer use. The first block of the content keeps clear of it. Use PopoverClose for any other close action, such as a Done button. Escape and clicks outside the popover also close it.

"use client";

import { BellIcon } from "lucide-react";
import { Button } from "@/siteplane/components/button";
import {
  Popover,
  PopoverDescription,
  PopoverPopup,
  PopoverTitle,
  PopoverTrigger,
} from "@/siteplane/components/popover";

export default function Example() {
  return (
    <Popover>
      <PopoverTrigger
        render={
          <Button aria-label="Notifications" size="icon-md" variant="outline" />
        }
      >
        <BellIcon />
      </PopoverTrigger>
      <PopoverPopup className="w-72" showCloseButton>
        <PopoverTitle>Notifications</PopoverTitle>
        <PopoverDescription>
          You&apos;re all caught up. New mentions and assignments will appear
          here.
        </PopoverDescription>
      </PopoverPopup>
    </Popover>
  );
}

Alignment

Use align to line the popover up with the start (default), center or end of its trigger. side sets the preferred side; the popover flips when there isn’t enough room.

"use client";

import { Button } from "@/siteplane/components/button";
import {
  Popover,
  PopoverDescription,
  PopoverPopup,
  PopoverTitle,
  PopoverTrigger,
} from "@/siteplane/components/popover";

const alignments = [
  { align: "start", label: "Start" },
  { align: "center", label: "Center" },
  { align: "end", label: "End" },
] as const;

export default function Example() {
  return (
    <div className="flex flex-wrap gap-2">
      {alignments.map(({ align, label }) => (
        <Popover key={align}>
          <PopoverTrigger render={<Button variant="outline" />}>
            {label}
          </PopoverTrigger>
          <PopoverPopup align={align} className="w-56">
            <PopoverTitle>Project status</PopoverTitle>
            <PopoverDescription>
              On track: 12 of 18 tasks are done.
            </PopoverDescription>
          </PopoverPopup>
        </Popover>
      ))}
    </div>
  );
}

Tooltip style

Set tooltipStyle when an info button only reveals a short hint. Unlike a tooltip, the popover also opens on click or tap; openOnHover on the trigger opens it on hover as well.

"use client";

import { InfoIcon } from "lucide-react";
import { Button } from "@/siteplane/components/button";
import { Field, FieldLabel } from "@/siteplane/components/field";
import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/siteplane/components/input-group";
import {
  Popover,
  PopoverPopup,
  PopoverTrigger,
} from "@/siteplane/components/popover";

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Password</FieldLabel>
      <InputGroup>
        <InputGroupInput autoComplete="new-password" type="password" />
        <InputGroupAddon align="inline-end">
          <Popover>
            <PopoverTrigger
              openOnHover
              render={
                <Button
                  aria-label="Password requirements"
                  profile="field-action"
                  shape="square"
                />
              }
            >
              <InfoIcon />
            </PopoverTrigger>
            <PopoverPopup align="center" side="top" tooltipStyle>
              Use at least 8 characters.
            </PopoverPopup>
          </Popover>
        </InputGroupAddon>
      </InputGroup>
    </Field>
  );
}

Glass

Set variant="glass" when the popover opens over a photo, a video or a color surface. The popup then shows a tinted, blurred view of what lies behind it; its padding and parts stay the same. Glass shows every component with a glass variant.

The gradient only makes the glass visible. It is not part of the component.

"use client";

import { Button } from "@/siteplane/components/button";
import {
  Popover,
  PopoverDescription,
  PopoverPopup,
  PopoverTitle,
  PopoverTrigger,
} from "@/siteplane/components/popover";

export default function Example() {
  return (
    <Popover>
      <PopoverTrigger render={<Button variant="glass" />}>Share</PopoverTrigger>
      <PopoverPopup className="w-72" variant="glass">
        <PopoverTitle>Share link</PopoverTitle>
        <PopoverDescription>
          Anyone with the link can view this page.
        </PopoverDescription>
      </PopoverPopup>
    </Popover>
  );
}

API reference

Component properties
PropTypeDefaultDescription
PopoverPopup.sidetop | right | bottom | left | inline-start | inline-endbottomPreferred side.
PopoverPopup.alignstart | center | endstartAlignment.
PopoverPopup.sideOffsetnumber4Gap from anchor.
PopoverPopup.alignOffsetnumber0Alignment offset.
PopoverPopup.variantdefault | glassdefaultglass shows a tinted, blurred view of what lies behind the popup.
PopoverPopup.tooltipStylebooleanfalseSmall tooltip-like visual surface.
PopoverPopup.viewportPaddingcompact | defaultdefaultViewport padding profile.
PopoverPopup.anchorPositioner anchortriggerCustom anchor.
PopoverPopup.showCloseButtonbooleanfalseRenders a close icon action in the top-end corner; the first content block keeps clear of it.
PopoverPopup.closePropsPopoverPrimitive.Close.PropsunsetOverrides the built-in close action, including aria-label (default: Close).
PopoverPopup.portalPropsPopoverPrimitive.Portal.PropsunsetPortal configuration.
PopoverTrigger.openOnHoverbooleanfalseAlso opens the popover on hover.
open / defaultOpenbooleanfalseVisibility.
onOpenChange(open, eventDetails) => voidunsetVisibility callback.
onOpenChangeComplete(open) => voidunsetAfter motion completes.

Popover is the foundation Root. Trigger, Close, Title and Description wrap the corresponding parts. Popup includes Portal, Positioner, Viewport and the optional close button; PopoverContent aliases Popup. PopoverCreateHandle creates a detached-trigger handle; PopoverPrimitive exports the namespace. Styled parts accept string className. Popup focus and Root interaction options follow Base UI Popover; the wrapper only forwards the listed positioner options, not arbitrary positioner props.

Read the installed source.

Usage notes

Use Escape to dismiss and preserve focus restoration. Put fields and buttons in a popover rather than in a Tooltip, whose content cannot be reached with the keyboard. The popup carries the Siteplane UI provider settings into its portal, so no additional provider is required.