狀態與回饋系統

UI Sound Kit

用 Web Audio 即時合成 click、hover、success、error 等輕量介面音效,內建音量控制、全域開關與偏好尊重,零音檔零額外請求。

把這個效果裝進你的專案

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

npx webberui@latest add ui-sound-kit

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

UI Sound Kit 是 React 的介面音效(UI sound effects)套件,用瀏覽器的 Web Audio API 即時合成按鍵、通知、成功與錯誤提示音,透過 React Context 提供 useSound() hook,附帶的按鈕以 Tailwind CSS 撰寫。

Playground

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

0.5
編輯中的呼叫預設屬性
<SoundButton/>

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

適用情境

  • 表單送出、付款完成、上傳失敗等關鍵操作的回饋音,讓結果更容易被注意到。
  • 聊天、客服或協作工具收到新訊息時的通知提示音。
  • 遊戲化介面、互動式導覽或兒童教育產品的按鍵音效。
  • 設定頁的「介面音效」開關與音量調整,偏好會自動記在瀏覽器裡。
  • 不適合:播放自訂音檔或背景音樂。內建音效是固定的六種合成音,沒有載入音檔的 API。

安裝

npx webberui@latest add https://webberui.com/r/ui-sound-kit.json

或在 components.json 設定 registries 後,改用 @webberui/ui-sound-kit 安裝。

使用

在需要音效的範圍外層包一次 SoundProvider,內部任何元件即可透過 useSound() 取得 play() 與開關/音量控制:

import {
  SoundProvider,
  useSound,
} from "@/components/ui/ui-sound-kit";

function App() {
  return (
    <SoundProvider defaultVolume={0.5}>
      <SaveButton />
    </SoundProvider>
  );
}

function SaveButton() {
  const { play } = useSound();
  return (
    <button
      onClick={async () => {
        try {
          await save();
          play("success");
        } catch {
          play("error");
        }
      }}
    >
      儲存
    </button>
  );
}

內建音效名稱:"click"、"hover"、"success"、"error"、"toggle"、"notification"(可從 SOUND_NAMES 取得完整清單)。

內建元件

SoundButton 是會發聲的按鈕,點擊(與可選的 hover)時自動播放;SoundToggle 是全域開關按鈕:

import {
  SoundButton,
  SoundToggle,
} from "@/components/ui/ui-sound-kit";

<SoundButton sound="click" hoverSound="hover">
  播放
</SoundButton>

<SoundToggle />

範例

在 Next.js 根版面掛上 Provider

SoundProvider 是 client component,可以直接放在 Server Component 的 app/layout.tsx 裡包住 children。

app/layout.tsx
import { SoundProvider } from "@/components/ui/ui-sound-kit";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-Hant">
      <body>
        <SoundProvider defaultVolume={0.4}>{children}</SoundProvider>
      </body>
    </html>
  );
}

設定頁的開關與音量滑桿

用 useSound() 回傳的 volume 與 setVolume 接一個原生滑桿,SoundToggle 負責全域開關。

"use client";

import { SoundToggle, useSound } from "@/components/ui/ui-sound-kit";

export function SoundSettings() {
  const { volume, setVolume, enabled } = useSound();

  return (
    <div className="flex items-center gap-3">
      <SoundToggle />
      <input
        type="range"
        min={0}
        max={1}
        step={0.05}
        value={volume}
        disabled={!enabled}
        onChange={(e) => setVolume(Number(e.target.value))}
        aria-label="介面音效音量"
      />
    </div>
  );
}

收到新訊息時播放通知音

play() 的第二個參數可以設定這一次的音量倍率,疊在全域音量之上。

"use client";

import * as React from "react";
import { useSound } from "@/components/ui/ui-sound-kit";

export function useMessageSound(socket: WebSocket) {
  const { play } = useSound();

  React.useEffect(() => {
    const onMessage = () => play("notification", { volume: 0.6 });
    socket.addEventListener("message", onMessage);
    return () => socket.removeEventListener("message", onMessage);
  }, [socket, play]);
}

Props

SoundProvider

Prop型別預設值說明
defaultEnabledbooleantrue初始是否開啟音效(有持久化偏好時掛載後覆蓋)
defaultVolumenumber0.5初始全域音量 0~1
storageKeystring | null"wb-sound-kit"持久化開關與音量的 localStorage 鍵;null 停用持久化
respectReducedMotionbooleantrue系統開啟「減少動態效果」時是否一併靜音

useSound() 回傳

名稱型別說明
play(name, options?) => void合成並播放內建音效,options.volume 為單次音量倍率
enabledboolean全域音效是否開啟
setEnabled(enabled: boolean) => void開啟/關閉全域音效
volumenumber全域音量 0~1
setVolume(volume: number) => void設定全域音量(自動夾在 0~1)

SoundButton

Prop型別預設值說明
soundSoundName"click"點擊時播放的音效
hoverSoundSoundName—指定時,游標移入也會播放此音效

其餘原生 button 屬性皆透傳。SoundToggle 亦透傳原生 button 屬性,並以 chime(預設 true)控制開啟時是否播放確認音。

細節

  • 即時合成,零資源:以 Web Audio 的 OscillatorNode + GainNode 即時合成音效,不載入任何音檔、不發任何額外請求。
  • 惰性初始化:AudioContext 於首次 play()(通常來自使用者點擊手勢)才建立並自動 resume,符合瀏覽器自動播放政策;分頁切回若被 suspend 也會嘗試恢復。
  • 單一音訊圖:整個 Provider 共用一個 AudioContext 與主音量節點;每次播放另接一段獨立 bus 增益,套用單次音量倍率後回收。
  • 持久化偏好:開關與音量預設寫入 localStorage,重新整理後沿用;可用 storageKey={null} 關閉。

可及性

  • SoundToggle 以 aria-pressed 表示開關狀態,並隨狀態切換 aria-label(開啟音效/關閉音效)與圖示。
  • 音量滑桿為原生 <input type="range">,帶 aria-label,可鍵盤操作。
  • 使用者系統開啟「減少動態效果」時,respectReducedMotion 預設會一併靜音,尊重降噪偏好;音效關閉時互動行為(onClick 等)不受影響,只是不發聲。

常見問題

可以在 Next.js App Router 使用嗎?

可以。原始碼已有 "use client",SoundProvider 可以直接放在 Server Component 的版面裡。useSound()、SoundButton 與 SoundToggle 都必須在 SoundProvider 範圍內使用,否則 useSound() 會拋出錯誤。

為什麼頁面載入後第一次沒有聲音?

瀏覽器的自動播放政策要求音訊由使用者操作觸發。AudioContext 會在第一次呼叫 play() 時才建立並嘗試 resume,所以應該從點擊等事件裡呼叫,而不是在頁面載入時自動播放。

需要準備音檔嗎?

不需要。所有音效都是用 OscillatorNode 與 GainNode 即時合成,不載入音檔、不發額外請求,依賴只有 motion(偵測減少動態效果設定)與 lucide-react(開關圖示)。

使用者的偏好會被記住嗎?

會。開關與音量預設存在 localStorage 的 "wb-sound-kit" 鍵,可以用 storageKey 換成別的鍵,或傳 null 停用。

開啟減少動態效果時會發聲嗎?

預設不會。respectReducedMotion 預設為 true,系統開啟減少動態效果時 play() 不發聲,按鈕的 onClick 等行為照常執行;不需要這個行為時傳 respectReducedMotion={false}。