Chuyển tới nội dung
Component

Popover

A small panel anchored to the button that opens it, for content the user acts in without leaving the page: a short form, filters, a share link. It sits on the raised surface with a border and a medium shadow, and closes on Escape, an outside press or its trigger.

Xem trước

Props

  • open
  • defaultOpen
  • side
  • align
  • sideOffset
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from "@/components/ui/popover"

<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Share</Button>
  </PopoverTrigger>
  <PopoverContent aria-label="Share this project" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Done</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>

Cách dùng

Khi nào dùng

  • A few controls next to the button that opens them: a share link, filters, a quick setting.
  • Content the user acts in without leaving the page, which closes on Escape or a press outside.

Khi nào không dùng

  • A list of actions. Use DropdownMenu.
  • A hint on hover or focus, with no controls. Use Tooltip.
  • A long form, or input the user must not lose on a press outside. Use Dialog.

Biến thể

side

Set on PopoverContent. The side of the trigger it opens on. It flips when there is no room.

  • top

  • right

  • bottommặc định

  • left

import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from "@/components/ui/popover"

<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Share</Button>
  </PopoverTrigger>
  <PopoverContent side="top" aria-label="Share this project" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Done</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Share</Button>
  </PopoverTrigger>
  <PopoverContent side="right" aria-label="Share this project" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Done</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Share</Button>
  </PopoverTrigger>
  <PopoverContent aria-label="Share this project" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Done</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Share</Button>
  </PopoverTrigger>
  <PopoverContent side="left" aria-label="Share this project" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Done</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>

align

Set on PopoverContent. How it lines up with the trigger along that side. Use start or end when the trigger sits near an edge.

  • start

  • centermặc định

  • end

import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from "@/components/ui/popover"

<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Share</Button>
  </PopoverTrigger>
  <PopoverContent align="start" aria-label="Share this project" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Done</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Share</Button>
  </PopoverTrigger>
  <PopoverContent aria-label="Share this project" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Done</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Share</Button>
  </PopoverTrigger>
  <PopoverContent align="end" aria-label="Share this project" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Done</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>

Ví dụ

import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from "@/components/ui/popover"

<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Share</Button>
  </PopoverTrigger>
  <PopoverContent aria-labelledby="share-heading" className="flex flex-col gap-md">
    <p id="share-heading" className="text-md font-medium">Share this project</p>
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Done</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>

Next to an edge

align end keeps the panel on screen when the trigger sits at the right of a toolbar.

import { IconList } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"

<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary" iconOnly aria-label="Filters">
      <IconList size={16} />
    </Button>
  </PopoverTrigger>
  <PopoverContent align="end" aria-label="Filters" className="flex flex-col gap-md">
    <Checkbox label="Only my projects" />
    <Checkbox label="Show archived" />
  </PopoverContent>
</Popover>

Nên và không nên

Nên

  • Keep it short: a few controls, a link to copy, a set of filters.
  • Give the panel a heading, or an aria-label when it has none.
  • Apply changes at once, or close it with a PopoverClose button such as Done.

Không nên

  • Don't put a list of actions in it; use DropdownMenu.
  • Don't use it for a long form or anything the user must not lose on an outside press; use Dialog.
  • Don't open it on hover; use Tooltip for a hint.

Props

TênKiểuGiá trịMặc địnhMô tả
openboolean--Set on Popover. Whether it is open, when you control it. Pair it with onOpenChange.
defaultOpenboolean-falseSet on Popover. Opens it on first render, when you don't control it.
onOpenChangefunction--Set on Popover. Called with true or false when it opens or closes (trigger, Escape, an outside press, PopoverClose).
modalboolean-falseSet on Popover. true keeps focus inside and hides the rest of the page from assistive tech until it closes. Leave it false for a panel the user works next to.
sideenumtop | right | bottom | leftbottomSet on PopoverContent. The side of the trigger it opens on. It flips when there is no room.
alignenumstart | center | endcenterSet on PopoverContent. How it lines up with the trigger along that side. Use start or end when the trigger sits near an edge.
sideOffsetnumber-4Set on PopoverContent. Space between the trigger and the panel, in px. 4 is spacing-xs.

Slot

TênBắt buộcMô tả
triggercóPopoverTrigger with asChild around a Button.
contentcóPopoverContent with a heading or an aria-label, and the controls.

Cấu tạo

PhầnMô tả
rootPopover. Holds the open state; renders nothing itself.
triggerPopoverTrigger: the button that opens it, usually a Button with asChild.
contentPopoverContent: the panel. Raised fill, border, radius-container, shadow-med, px-lg py-md, 288px wide and never wider than the screen minus spacing-sm on each side.
anchorPopoverAnchor (optional): another element to position the panel against.
closePopoverClose (optional): a button inside that closes it, such as Done.

Khả năng truy cập

Role: dialog

PhímHành động
Space, EnterOn the trigger: opens or closes the popover. Focus moves to the first focusable element inside, or to the panel.
TabMoves through the controls inside. With modal, focus stays inside; otherwise it can leave, and the popover stays open.
EscapeCloses the popover and returns focus to the trigger.
  • The trigger gets aria-expanded and aria-controls, and the panel is a dialog. Name it with aria-label or aria-labelledby when it has no heading.
  • An outside press closes it. Put anything the user must not lose (unsaved input) in a Dialog instead.
  • Not for a list of actions: that is a DropdownMenu, which has menu semantics and arrow keys.
  • Not for a hint on hover: that is a Tooltip, which holds no controls.

Cài đặt

npx shadcn add https://werewolf-ui.vercel.app/r/popover.json

Lệnh này thêm luôn styles/werewolf.css, bộ token mà mọi component dùng, và các component khác mà nó cần. Import file đó một lần trong CSS gốc, ngay sau @import "tailwindcss", với đường dẫn tính từ file CSS đó (từ app/globals.css là @import "../styles/werewolf.css").