WebberUI

Dynamic Island Nav

靈動島式膠囊導航:選中膠囊在項目間滑動形變,選中項目名稱由 0 展開,可再點一次讓島身向下 morph 展開面板。

載入預覽⋯

Playground

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

<DynamicIslandNav />

安裝

npx shadcn@latest add https://webberui.com/r/dynamic-island-nav.json

或在 components.json 設定 registries 後,改用 @webberui/dynamic-island-nav 安裝。

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

npm install motion lucide-react clsx tailwind-merge

使用

<DynamicIslandNav> 包住一組 <DynamicIslandNavItem>。每個項目只需 valueiconlabel;若提供 children,該項目就能在選中後再點一次向下展開靈動島面板。

import { Bell, Home, Search } from "lucide-react";
import {
  DynamicIslandNav,
  DynamicIslandNavItem,
} from "@/components/ui/dynamic-island-nav";

<DynamicIslandNav defaultValue="home">
  <DynamicIslandNavItem value="home" icon={<Home aria-hidden />} label="首頁" />
  <DynamicIslandNavItem
    value="search"
    icon={<Search aria-hidden />}
    label="搜尋"
  />
  <DynamicIslandNavItem value="alerts" icon={<Bell aria-hidden />} label="通知">
    <p>你有 3 則新通知</p>
  </DynamicIslandNavItem>
</DynamicIslandNav>

受控模式

傳入 valueonValueChange 即進入受控模式,選中狀態改由外部管理:

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

<DynamicIslandNav value={value} onValueChange={setValue}>
  {/* …items… */}
</DynamicIslandNav>

固定於底部

position="fixed-bottom" 會把整座島固定於視窗底部置中(z-50),外層走廊不吃指標事件,適合當作 App 級的浮動導覽:

<DynamicIslandNav position="fixed-bottom" defaultValue="home">
  {/* …items… */}
</DynamicIslandNav>

Props

DynamicIslandNav

Prop型別預設值說明
childrenReact.ReactNode一組 <DynamicIslandNavItem> 子項
valuestring受控的選中值
defaultValuestring第一個項目非受控模式的初始選中值
onValueChange(value: string) => void選中值變更時的回呼
position'inline' | 'fixed-bottom''inline'inline 隨文件流;fixed-bottom 固定於視窗底部置中
labelstring'導覽'tablist 的無障礙名稱
classNamestring附加到膠囊本體的 class

DynamicIslandNavItem

Prop型別預設值說明
valuestring項目的唯一識別值
iconReact.ReactNode左側圖示,會被限制在 16px 內
labelstring項目名稱,作為 aria-label 並在選中時展開顯示
disabledbooleanfalse停用此項目,無法選取或聚焦
childrenReact.ReactNode選中時向下展開的面板內容;提供時該項目可展開,未提供則純切換
classNamestring附加到此項目按鈕的 class

DynamicIslandNavItem 為宣告式設定元件,本身不渲染節點;按鈕與展開面板皆由 DynamicIslandNav 讀取其 props 統一渲染,因此請務必作為 DynamicIslandNav 的直接子項使用。

細節

  • 選中膠囊滑動:選中背景是一個帶共享 layoutIdmotion.span,切換項目時舊項目卸載、新項目掛載同一個 layoutId,由 Motion 以 spring 在按鈕之間滑動並同步形變寬度。
  • 名稱展開:僅選中項目渲染名稱,寬度自 0 動畫到 autoAnimatePresence),帶動整條膠囊左右形變;未選中項目只留圖示。
  • 島身展開:選中「有 children 的項目」後再點一次,面板高度自 0 morph 到 auto,父膠囊隨內容自然長高,右側箭頭旋轉 180° 標示展開狀態;切到不同項目時 key 綁定選中值使面板重播進場。
  • 展開規則:切到新項目時,有面板即自動展開、無面板則收合;再次點擊目前項目則切換其展開狀態。

可及性

  • 膠囊列為 role="tablist",每個項目為 role="tab" 並帶 aria-selected;可展開項目帶 aria-expandedaria-controls,展開面板為 role="region" 並以 aria-labelledby 指回對應項目。
  • 採 roving tabindex:僅選中項目 tabIndex=0 / 在項目間移動焦點並選取,Home / End 跳到首尾,停用項目自動略過。
  • 每個項目都帶 aria-label(即使未選中、名稱未展開時),螢幕閱讀器仍可讀出完整名稱;動畫節點一律 aria-hidden,另有 role="status" 的純文字狀態播報選中與展開狀態。
  • 使用者系統開啟「減少動態效果」時,膠囊滑動、名稱展開與面板 morph 全部歸零,退化為即時切換與短暫淡入淡出,不產生位移感。

On this page