覆蓋層與回饋

錨定浮層

按鈕觸發的錨定浮層:依 side/align 定位並在視窗邊緣自動翻轉,淡入、微縮放、沿方向滑入,焦點進出與點外關閉完整處理。

由按鈕觸發、貼著觸發鈕出現的浮層,適合放篩選條件、日期區間、快速設定這類「不值得開一個對話框」的小表單。以複合元件組成:Popover.Trigger、Popover.Content、Popover.Close、Popover.Arrow。浮層依 side/align 定位,碰到視窗邊緣會翻到對側並沿交叉軸推回視窗內;進場是淡入加 0.95 微縮放、沿方向滑入 8px,縮放原點跟著實際落點。開啟時焦點移進浮層、關閉時回到觸發鈕,點外與 Escape 都能關閉。定位邏輯自帶在單一檔案內,不依賴任何定位套件。

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add popover

還沒有專案,或安裝遇到問題?看快速開始 →

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

100
4
編輯中的呼叫預設屬性
<Popover/>

完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。

安裝

npx webberui@latest add https://webberui.com/r/popover.json

或在 components.json 設定 registries 後,改用 @webberui/popover 安裝。

使用

import { Popover } from "@/components/ui/popover";

<Popover>
  <Popover.Trigger>設定期間</Popover.Trigger>
  <Popover.Content side="bottom" align="start" aria-labelledby="range-title">
    <Popover.Arrow />
    <p id="range-title" className="text-sm font-medium">報表期間</p>
    {/* 你的表單內容 */}
    <Popover.Close>完成</Popover.Close>
  </Popover.Content>
</Popover>

Props

Prop型別預設值說明
openboolean—受控的開啟狀態;未提供時由元件內部管理(非受控)
defaultOpenbooleanfalse非受控模式的初始開啟狀態
onOpenChange(open: boolean) => void—開啟狀態變動時回呼
modalbooleanfalse模態模式:Tab 焦點鎖在浮層內、鎖住頁面捲動、後方加透明點擊攔截層
childrenReactNode—Popover.Trigger 與 Popover.Content 等子元件

Popover.Trigger

欄位型別預設值說明
asChildbooleanfalse為 true 時不渲染按鈕,改把 ref、點擊與 aria 屬性合進唯一的子元素
childrenReactNode—觸發鈕內容
classNamestring—透傳到觸發鈕,以 cn() 併入預設樣式

其餘 button 屬性(aria-label、disabled、onClick…)會透傳到按鈕上。

Popover.Content

欄位型別預設值說明
side"top" | "right" | "bottom" | "left""bottom"浮層相對觸發鈕的方向;空間不足時會自動翻轉到對側
align"start" | "center" | "end""center"交叉軸對齊方式
sideOffsetnumber4與觸發鈕之間的距離(px)
alignOffsetnumber0交叉軸上的位移(px),正值往內縮
collisionPaddingnumber8碰撞偵測時與視窗邊緣保留的距離(px)
avoidCollisionsbooleantrue是否在超出視窗時翻轉方向並沿交叉軸推回視窗內
durationnumber100進出場動畫時長(毫秒),同時寫入 --wb-popover-duration
onEscapeKeyDown(event: KeyboardEvent) => void—按下 Escape 時回呼;呼叫 event.preventDefault() 可阻止關閉
onInteractOutside(event: PointerEvent) => void—在浮層與觸發鈕之外按下指標時回呼;呼叫 event.preventDefault() 可阻止關閉
aria-labelledbystring—指向浮層標題元素的 id,供螢幕閱讀器朗讀對話框名稱
aria-labelstring—沒有可見標題時,直接提供對話框名稱
childrenReactNode—浮層內容
classNamestring—透傳到浮層根元素,以 cn() 併入預設樣式

Popover.Close

欄位型別預設值說明
childrenReactNode—按鈕內容;未提供時顯示 × 圖示(並帶 aria-label="關閉")
classNamestring—透傳到關閉鈕,以 cn() 併入預設樣式

Popover.Arrow

欄位型別預設值說明
widthnumber10箭頭底邊寬度(px)
heightnumber5箭頭高度(px)
classNamestring—透傳到箭頭 SVG,可用 fill-*/stroke-* 改色

另外匯出型別 PopoverSide 與 PopoverAlign。

細節

  • 定位:浮層以 portal 掛到 document.body、position: fixed,座標取整避免文字模糊。主軸貼在觸發鈕外側 sideOffset px;溢出視窗且對側空間較大時翻轉,交叉軸則 clamp 回視窗內(浮層比視窗還寬時貼齊左邊)。
  • 重算時機:開啟當下同步量一次,之後觀察觸發鈕與浮層的尺寸變化、視窗縮放與所有祖先容器的捲動,以 requestAnimationFrame 節流成每幀最多一次;關閉時全部移除。
  • 第一次量測完成前浮層保持 visibility: hidden,不會在左上角閃一下;量完才開始進場動畫。
  • 浮層根元素帶 data-side 與 data-align(翻轉後的實際值)與 data-state,方便寫依方向而異的樣式。
  • 動畫節奏外露為 --wb-popover-duration:浮層內的 CSS transition 都讀這個變數,可在全域覆寫。
  • 箭頭指向觸發鈕中心,並避開浮層圓角;填色與描邊和浮層同色,蓋住相接處的邊框。
  • 巢狀浮層:Escape 只關閉最上層;點擊落在子浮層內不會讓父浮層關閉。

可及性

  • 觸發鈕帶 aria-haspopup="dialog"、aria-expanded,開啟時以 aria-controls 指向浮層;浮層為 role="dialog",可用 aria-labelledby 或 aria-label 命名,modal 時加上 aria-modal
  • 開啟後焦點移到浮層內第一個可聚焦元素(沒有就聚焦浮層本身);以任何方式關閉時,若焦點仍在浮層內就回到觸發鈕
  • 非 modal:Tab 越過最後一個元素會移到觸發鈕之後的元素並關閉浮層,Shift+Tab 越過第一個元素回到觸發鈕;modal:Tab/Shift+Tab 在浮層內循環
  • Escape、在浮層外按下指標、Popover.Close 都會關閉浮層;所有按鈕都有 focus-visible 外框
  • 使用者系統開啟「減少動態效果」時,只保留淡入淡出(時長不變),不縮放、不位移