Mark up your HTML with data-sound-* attributes and call bind() once, or call play() directly whenever you need exact control.
$ npm install chordal
ESM-only, zero runtime dependencies, safe to import during SSR.
<button data-sound-hover data-sound-click>Save</button>
import { bind } from 'chordal'; bind();
bind() scans your markup once and wires up every data-sound-* element it finds.
| Attribute | Fires on | Instance |
|---|---|---|
| data-sound-hover | pointerenter | hover |
| data-sound-click | pointerdown | click |
| data-sound-success | click | success |
| data-sound-error | invalid | error |
| data-sound-toggle | click | toggle |
| data-sound-sent | click | sent |
| data-sound-notification | click | notification |
data-sound-click="chime") to override just that element.data-sound-* attribute at all — listening and delete take a call each to start and stop or confirm, and slider is driven by playContinuous(), not a discrete DOM event. Call these directly; see the API tab.data-sound-toggle reads on/off state from the element's own aria-pressed attribute (or a checkbox's checked property) at the moment it's clicked. Wire up your own toggle logic first, then call bind() after it, so it always reads the post-click value:<button id="dark-mode-toggle" aria-pressed="false" data-sound-toggle>Dark mode</button>
const toggleBtn = document.querySelector('#dark-mode-toggle'); toggleBtn.addEventListener('click', () => { const isOn = toggleBtn.getAttribute('aria-pressed') === 'true'; toggleBtn.setAttribute('aria-pressed', String(!isOn)); }); bind(); // after your own toggle logic — reads aria-pressed at click time
The direct, imperative trigger, for moments that are not a DOM interaction: an async action that just resolved, or a state your own code already knows about.
toggle and listening are stateful — pass which state just became active:
play('success', { family: 'glass-crystal' }); play('listening', { state: 'on' }); // mic session started play('listening', { state: 'off' }); // mic session stopped
delete has no data-sound-delete attribute — call it yourself, after your own confirmation step, not on a raw click:
await confirmDelete(item); play('delete'); item.remove();
Scans root (defaults to document) for attributes and wires listeners. Safe to call again: already-bound elements are skipped.
For a range slider's input event, pass the current 0–1 ratio. Debounced and pitch-quantized internally.
The family used when a call doesn't override its own.
Global on/off switch for the master output.
Exported readonly arrays for building your own picker UI.
Embed <chordal-playground> for an interactive family/instance browser. It's a dev-time tool, not for production pages.
A condensed reference built for AI coding agents: install steps, the family/instance model, and the behavioral details a generated integration would otherwise get wrong.
View AGENTS.md ↗