Sonidos y hápticos
24 sonidos de interfaz sintetizados en el navegador en el momento en que suenan: sin archivos de audio, nada que alojar, un módulo de 7 KB. Cada sonido es una pequeña receta de dos o tres parciales senoidales sobre una pentatónica de do mayor, un ataque suave y un clic filtrado, mezclados a −14 dBFS. Los sonidos están apagados hasta que el visitante los activa, se recuerdan por navegador y nunca suenan antes de un gesto del usuario. Movimiento y sonido son preferencias separadas: prefers-reduced-motion no los cambia.
data-sound solo suenan cuando esto está activado.Instalación
npx shadcn@latest add https://goodui.dev/r/sounds.jsonAterriza en lib/goodui-sounds.ts. Los componentes con una prop sound lo listan como dependencia, así que viene con ellos. El módulo se carga de forma perezosa y solo cuando un componente tiene sound activado, así que una app que nunca lo usa no incluye nada de él.
Todos los sonidos
Haz clic en una tarjeta para oírlo. La forma de onda es el sonido renderizado sin conexión, normalizado para que los más silenciosos se sigan leyendo.
Componentes
Pasa sound para reproducir el sonido predeterminado del componente, o el nombre de un sonido para elegir otro. Todo está apagado a menos que lo pases.
button: tap al hacer clicstateful-button: tap y luego success o error según el resultadoswitch: toggle-on / toggle-offcheckbox: pop al marcar, tick al desmarcartabs: tick al seleccionarrating: un tick por estrella bajo el puntero, cada vez más agudo a lo largo de la fila, pop al elegirnumber-input: tick por paso, más agudo para +, más grave para −dialog: open / closetoast-stack (Toaster): success, error, warning o notify por cada toast nuevo, y de nuevo cuando un toast de carga se resuelveconfetti: celebrate con la explosión (fireConfetti({ sound: true }) o <Confetti sound />)hold-to-confirm: tick cuando empieza la pulsación, confirm cuando se completacopy-button: pop tras copiar, error cuando falla
<Button sound onClick={save}>Save</Button>
<Switch sound label="SMS reminders" />
<Rating sound defaultValue={4} />
<Dialog sound title="Cancel this booking?" onConfirm={cancel} trigger={<Button variant="outline">Cancel</Button>} />
<Toaster sound /> {/* success, error, warning or notify per toast */}
<HoldToConfirm sound="unlock" onConfirm={deleteAccount}>Delete account</HoldToConfirm>Dale el interruptor a la gente
Monta SoundProvider una vez y pon un interruptor en tus ajustes o en la cabecera. La elección del visitante se guarda en localStorage y gana al valor predeterminado de la app.
import { SoundProvider } from "@/lib/goodui-sounds"
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<SoundProvider>{children}</SoundProvider> {/* enabled: app default, persist, volume, attributes */}
</body>
</html>
)
}"use client"
import { useSound } from "@/lib/goodui-sounds"
export function SoundToggle() {
const { enabled, toggle, play } = useSound()
return (
<button type="button" role="switch" aria-checked={enabled} onClick={() => { toggle(); play(enabled ? "toggle-off" : "toggle-on", { force: true }) }}>
Sounds {enabled ? "on" : "off"}
</button>
)
}Cualquier elemento
Con el provider montado, cualquier elemento puede reproducir un sonido sin código. data-sound suena al hacer clic (toggle lee el estado marcado después del clic), data-sound-hover cuando entra el puntero.
<button data-sound="tap">Book</button>
<input type="checkbox" data-sound="toggle" />
<a href="/pricing" data-sound-hover>Pricing</a> {/* "hover", very subtle */}API
import { play, useSound, setSoundEnabled, isSoundEnabled, setSoundVolume, SOUND_NAMES, type SoundName } from "@/lib/goodui-sounds"
play("success") // false when sounds are off, before the first gesture, or without Web Audio
play("tick", { rate: 1.1, volume: 0.6 }) // rate: pitch and speed, volume: 0..1 on top of the -14 dBFS bus
play("notify", { force: true }) // previews and settings: ignores the toggle
setSoundEnabled(true) // saved in localStorage (unless <SoundProvider persist={false} />)
setSoundVolume(0.5)
const { enabled, toggle, setEnabled, play } = useSound() // reads "off" during SSRDiseña el tuyo
Una receta es una lista de voces: parciales de oscilador o ruido filtrado, cada una con un inicio, una duración, un nivel y un glide opcional. tone() da una nota con su parcial de octava, noise() un clic o un whoosh. Mantenlo corto (menos de 350 ms), silencioso (niveles por debajo de 0.5) y dentro de la escala, y encajará junto a los integrados.
import { defineSound, tone, noise, play } from "@/lib/goodui-sounds"
// A cash-register "cha-ching": a click, then two notes a fifth apart with a bright octave.
defineSound("cha-ching", [
noise(0.02, { gain: 0.2, filter: 3000 }),
...tone(1046.5, 0.12, { at: 0.03, gain: 0.4, shine: 0.3 }), // C6
...tone(1568, 0.26, { at: 0.11, gain: 0.42, shine: 0.3 }), // G6
])
play("cha-ching") // or <button data-sound="cha-ching">
defineSound("tap", [...]) // built-ins can be replaced the same wayCampos de una voz: wave (sine, triangle, square, sawtooth o noise), freq y to en Hz, glide, at, dur y attack en segundos, gain 0..1, filter / filterTo / q para el paso bajo (osciladores) o el paso banda (ruido), detune en cents.
Pruebas
renderSound(name) renderiza una receta a través de un OfflineAudioContext y devuelve el buffer, así que un test puede comprobar que un sonido no es silencioso ni satura sin reproducir nada.
const buffer = await renderSound("success")
const data = buffer.getChannelData(0)
const peak = Math.max(...data.map(Math.abs)) // the built-ins stay under 0.3 at volume 1goodui Native: hápticos
Los mismos nombres son patrones hápticos en goodui Native (foundation/haptics.ts), construidos con los toques de impacto, notificación y selección de expo-haptics con su temporización. cue(name) de foundation/sounds reproduce el patrón y el sonido en una sola llamada, así que una pantalla escrita una vez se siente bien en todas las plataformas. Los sonidos están apagados hasta que la app llama a setSoundsEnabled(true); en iOS y Android suenan a través de un reproductor que la app registra con setSoundPlayer (expo-audio con sus propios archivos cortos), y en Expo web se sintetizan como aquí.
tap: impacto ligeroselect, tick, count-tick: seleccióntoggle-on / toggle-off: impacto ligero → medio / medio → ligero, con 60 ms de separaciónsuccess, warning, error: notificación del mismo tiponotify: dos impactos ligeros, con 90 ms de separaciónsend / receive: ligero → rígido / suave → ligerodelete: impacto fuerteundo: ligero → mediopop: impacto rígidowhoosh / slide: tres / dos seleccionesconfirm: impacto medio y luego notificación de éxitoopen / close: suave → ligero / ligero → suavedrag-start / drop: impacto medio / fuerte → ligerocelebrate: ligero, ligero, medio y luego notificación de éxitounlock: rígido, rígido y luego notificación de éxitohover: nada (no hay hover en pantallas táctiles)
import { cue, setSoundsEnabled } from "@/components/goodui-native/foundation"
setSoundsEnabled(settings.sounds) // from your settings screen
cue("confirm") // haptic pattern + sound, one vocabulary