WebberUI

Roving Highlight System

單一共享的變形高亮面,可跨 tabs、側欄、網格等容器在任何註冊元素間旅行,內建 roving tabindex 鍵盤導航與焦點環同步。

載入預覽⋯

Playground

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

<RovingHighlightSystem />

安裝

npx shadcn@latest add https://webberui.com/r/roving-highlight-system.json

或在 components.json 設定 registries 後,改用 @webberui/roving-highlight-system 安裝。

安裝依賴後,從 registry JSON(/r/roving-highlight-system.jsonfiles[0].content)複製 roving-highlight-system.tsx 原始碼到你的 components/ui/ 目錄:

npm install motion clsx tailwind-merge

使用

把任意容器與其中的 RovingHighlightItem 包在同一個 RovingHighlight 內,整組便共享一面會旅行的高亮。選取跨越不同容器時,高亮面會平滑變形移動到新位置。

import {
  RovingHighlight,
  RovingHighlightItem,
} from "@/components/ui/roving-highlight-system";

<RovingHighlight defaultValue="overview" variant="solid">
  {/* 容器一:分頁列 */}
  <div className="flex gap-1">
    <RovingHighlightItem value="overview">總覽</RovingHighlightItem>
    <RovingHighlightItem value="activity">動態</RovingHighlightItem>
  </div>

  {/* 容器二:側欄 —— 高亮會從分頁列一路旅行到這裡 */}
  <div className="flex flex-col gap-1">
    <RovingHighlightItem value="inbox">收件匣</RovingHighlightItem>
    <RovingHighlightItem value="starred">已加星</RovingHighlightItem>
  </div>
</RovingHighlight>;

受控模式

傳入 valueonValueChange 即進入受控模式,選取狀態由外部掌握:

const [value, setValue] = React.useState("overview");

<RovingHighlight value={value} onValueChange={setValue}>
  {/* ... */}
</RovingHighlight>;

焦點跟隨選取

selectOnFocus 開啟後,方向鍵移動焦點的同時即選取(自動模式),高亮直接跟著焦點走:

<RovingHighlight defaultValue="overview" selectOnFocus>
  {/* ... */}
</RovingHighlight>

Props

RovingHighlight

Prop型別預設值說明
valuestring | null受控選取值
defaultValuestring | nullnull非受控初始選取值
onValueChange(value: string) => void選取變更時觸發
variant"solid" | "outline" | "underline""solid"高亮款式:填色面/外框環/底線
orientation"horizontal" | "vertical" | "both""both"方向鍵導航方向
loopbooleantrue導航到端點時是否循環回另一端
selectOnFocusbooleanfalse焦點移到項目時是否同步選取
childrenReact.ReactNode任意容器與其中的 RovingHighlightItem

RovingHighlightItem

Prop型別預設值說明
valuestring此項目的唯一識別值(整組不可重複)
disabledbooleanfalse停用:不可選取、跳過鍵盤導航、不成為 tab 停靠點
onSelect(value: string) => void被選取時觸發(點擊或鍵盤 Enter/Space)
childrenReact.ReactNode項目內容(文字、圖示等)

其餘 button 原生屬性(aria-labelonClick 等)會一併轉發到底層元素。

細節

  • 跨容器旅行:整組同時只有被選取的項目渲染高亮面,切換時舊位置卸載、新位置掛載,由 Motion 的 layoutId 共享佈局動畫接手,讓高亮跨越不同容器平滑變形移動。
  • 命名空間隔離:每個 RovingHighlightLayoutGrouplayoutId 命名,同頁多個實例彼此的高亮不會互相配對。
  • 三款高亮solid 填色面、outline 外框環、underline 底線,皆會旅行。

可及性

  • roving tabindex:整組僅有一個 tab 停靠點(焦點 > 選取 > 第一個可用項目),Tab 進入後以方向鍵在所有已註冊項目間漫遊,Home / End 跳至首尾,Enter / Space 選取。
  • 焦點環同步:以近似 :focus-visible 的偵測,只有鍵盤操作時才顯示焦點環;焦點環同樣是共享元素,會隨焦點跨容器變形移動。
  • 選取項目帶 aria-current="true",停用項目帶 aria-disabled 並排除於鍵盤導航之外。
  • 使用者系統開啟「減少動態效果」時,高亮與焦點環改為直接出現在新位置,不再旅行。

On this page