Chuyển tới nội dung

Pro đang mở bán trước. Cài đặt mở từ tháng 1/2027. Xem Pro

Component

Popover

Panel nhỏ neo vào button mở nó, cho nội dung người dùng thao tác mà không rời trang: một form ngắn, bộ lọc, một link chia sẻ. Panel nằm trên bề mặt raised, có border và shadow medium, và đóng khi Escape, khi nhấn ra ngoài hoặc khi nhấn trigger.

  • npm: 1 trực tiếp, 2 tổng
  • registry: 2 trực tiếp, 2 tổng

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">Chia sẻ</Button>
  </PopoverTrigger>
  <PopoverContent aria-label="Chia sẻ dự án này" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Xong</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>

Cách dùng

Khi nào dùng

  • Vài control cạnh button mở chúng: một link chia sẻ, bộ lọc, một thiết lập nhanh.
  • Nội dung người dùng thao tác mà không rời trang, đóng khi Escape hoặc nhấn ra ngoài.

Khi nào không dùng

  • Một danh sách hành động. Dùng DropdownMenu.
  • Một gợi ý khi hover hoặc focus, không có control. Dùng Tooltip.
  • Một form dài, hoặc input người dùng không được mất khi nhấn ra ngoài. Dùng Dialog.

Biến thể

side

Đặt trên PopoverContent. Cạnh của trigger mà panel mở về. Lật khi không đủ chỗ.

  • 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">Chia sẻ</Button>
  </PopoverTrigger>
  <PopoverContent side="top" aria-label="Chia sẻ dự án này" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Xong</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Chia sẻ</Button>
  </PopoverTrigger>
  <PopoverContent side="right" aria-label="Chia sẻ dự án này" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Xong</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Chia sẻ</Button>
  </PopoverTrigger>
  <PopoverContent aria-label="Chia sẻ dự án này" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Xong</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Chia sẻ</Button>
  </PopoverTrigger>
  <PopoverContent side="left" aria-label="Chia sẻ dự án này" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Xong</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>

align

Đặt trên PopoverContent. Cách căn với trigger dọc theo cạnh đó. Dùng start hoặc end khi trigger nằm gần một mép.

  • 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">Chia sẻ</Button>
  </PopoverTrigger>
  <PopoverContent align="start" aria-label="Chia sẻ dự án này" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Xong</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Chia sẻ</Button>
  </PopoverTrigger>
  <PopoverContent aria-label="Chia sẻ dự án này" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Xong</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="secondary">Chia sẻ</Button>
  </PopoverTrigger>
  <PopoverContent align="end" aria-label="Chia sẻ dự án này" className="flex flex-col gap-md">
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Xong</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">Chia sẻ</Button>
  </PopoverTrigger>
  <PopoverContent aria-labelledby="share-heading" className="flex flex-col gap-md">
    <p id="share-heading" className="text-sm font-medium">Chia sẻ dự án này</p>
    <Field>
      <FieldLabel>Link</FieldLabel>
      <FieldControl>
        <Input readOnly defaultValue="https://example.com/p/42" />
      </FieldControl>
    </Field>
    <PopoverClose asChild>
      <Button>Xong</Button>
    </PopoverClose>
  </PopoverContent>
</Popover>

Sát một mép

align end giữ panel trên màn hình khi trigger nằm ở bên phải của một 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="Bộ lọc">
      <IconList size={16} />
    </Button>
  </PopoverTrigger>
  <PopoverContent align="end" aria-label="Bộ lọc" className="flex flex-col gap-md">
    <Checkbox label="Chỉ dự án của tôi" />
    <Checkbox label="Hiện đã lưu trữ" />
  </PopoverContent>
</Popover>

Nên và không nên

Nên

  • Giữ ngắn: vài control, một link để sao chép, một bộ lọc.
  • Cho panel một heading, hoặc một aria-label khi không có heading.
  • Áp thay đổi ngay, hoặc đóng bằng một button PopoverClose chẳng hạn Done.

Không nên

  • Đừng đặt một danh sách hành động vào panel; dùng DropdownMenu.
  • Đừng dùng cho form dài hoặc thứ người dùng không được mất khi nhấn ra ngoài; dùng Dialog.
  • Đừng mở khi hover; dùng Tooltip cho một gợi ý.

Props

TênKiểuGiá trịMặc địnhMô tả
openboolean--Đặt trên Popover. Có đang mở hay không, khi bạn điều khiển. Ghép với onOpenChange.
defaultOpenboolean-falseĐặt trên Popover. Mở ở lần render đầu, khi bạn không điều khiển.
onOpenChangefunction--Đặt trên Popover. Được gọi với true hoặc false khi mở hoặc đóng (trigger, Escape, nhấn ra ngoài, PopoverClose).
modalboolean-falseĐặt trên Popover. true giữ focus bên trong và ẩn phần còn lại của trang khỏi công nghệ hỗ trợ cho đến khi đóng. Để false cho panel mà người dùng làm việc ngay cạnh.
sideenumtop | right | bottom | leftbottomĐặt trên PopoverContent. Cạnh của trigger mà panel mở về. Lật khi không đủ chỗ.
alignenumstart | center | endcenterĐặt trên PopoverContent. Cách căn với trigger dọc theo cạnh đó. Dùng start hoặc end khi trigger nằm gần một mép.
sideOffsetnumber-4Đặt trên PopoverContent. Khoảng cách giữa trigger và panel, tính bằng px. 4 là spacing-xs.

Slot

TênBắt buộcMô tả
triggercóPopoverTrigger với asChild bọc một Button.
contentcóPopoverContent với một heading hoặc một aria-label, và các control.

Cấu tạo

PhầnMô tả
rootPopover. Giữ trạng thái mở; bản thân không render gì.
triggerPopoverTrigger: button mở panel, thường là một Button với asChild.
contentPopoverContent: panel. Nền raised, border, radius-container, shadow-med, px-lg py-md, rộng 288px và không bao giờ rộng hơn màn hình trừ spacing-sm mỗi bên.
anchorPopoverAnchor (tùy chọn): một phần tử khác để định vị panel theo.
closePopoverClose (tùy chọn): một button bên trong để đóng, chẳng hạn Done.

Khả năng truy cập

Role: dialog

PhímHành động
Space, EnterTrên trigger: mở hoặc đóng popover. Focus chuyển tới phần tử focus được đầu tiên bên trong, hoặc tới panel.
TabĐi qua các control bên trong. Với modal, focus ở lại bên trong; nếu không, focus có thể rời, và popover vẫn mở.
EscapeĐóng popover và trả focus về trigger.
  • Trigger nhận aria-expanded và aria-controls, và panel là một dialog. Đặt tên bằng aria-label hoặc aria-labelledby khi không có heading.
  • Nhấn ra ngoài thì đóng. Đặt những gì người dùng không được mất (input chưa lưu) vào Dialog.
  • Không dùng cho danh sách hành động: đó là DropdownMenu, có ngữ nghĩa menu và các phím mũi tên.
  • Không dùng cho gợi ý khi hover: đó là Tooltip, không chứa control.

Cài đặt

npx werewolf-ui add popover

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.