Chordal docs

Mark up your HTML with data-sound-* attributes and call bind() once, or call play() directly whenever you need exact control.

Install

$ npm install chordal

ESM-only, zero runtime dependencies, safe to import during SSR.

Quick start

index.html
<button data-sound-hover data-sound-click>Save</button>
app.ts
import { bind } from 'chordal';

bind();

bind() scans your markup once and wires up every data-sound-* element it finds.

Attributes

AttributeFires onInstance
data-sound-hoverpointerenterhover
data-sound-clickpointerdownclick
data-sound-successclicksuccess
data-sound-errorinvaliderror
data-sound-toggleclicktoggle
data-sound-sentclicksent
data-sound-notificationclicknotification
Leave a value empty to use the active family, or set one (data-sound-click="chime") to override just that element.
Three instances have no data-sound-* attribute at all — listening and delete take a call each to start and stop or confirm (see play()), and slider is driven by playContinuous(), not a discrete DOM event. Call these directly.

Toggle: reading state at click time

data-sound-toggle is the only attribute requiring custom setup. It reads aria-pressed (or checked) at click time—wire your toggle logic first so it reads the updated state:

index.html
<button id="dark-mode-toggle" aria-pressed="false" data-sound-toggle>Dark mode</button>
app.ts
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

Already have the state in your own code instead of reading it off the DOM? Call play('toggle', { state }) directly — see play().

bind(root?)

bind(root?: ParentNode): void

Scans root (defaults to document) for attributes and wires listeners. Safe to call again: already-bound elements are skipped.

play(instance, options?)

play(instance: SoundInstance, options?: PlayOptions): void

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('toggle', { state: 'on' });      // already know the state? skip data-sound-toggle entirely
play('listening', { state: 'on' });   // mic session started
play('listening', { state: 'off' });  // mic session stopped

For the declarative data-sound-toggle attribute instead — reading state off the DOM at click time — see Toggle.

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();

playContinuous(action, valueRatio, options?)

playContinuous(action: 'slider', valueRatio: number, options?: { family?: SoundFamily }): void

For a range slider's input event, pass the current 0–1 ratio. Debounced and pitch-quantized internally, so fast dragging reads as musical steps, not a siren sweep:

index.html
<input type="range" id="volume" min="0" max="100" value="50" />
app.ts
import { playContinuous } from 'chordal';

const slider = document.querySelector('#volume');
slider.addEventListener('input', () => {
  playContinuous('slider', Number(slider.value) / 100);
});

setFamily(family) / getFamily()

setFamily(family: SoundFamily): void
getFamily(): SoundFamily

The family used when a call doesn't override its own.

mute() / unmute() / isMuted()

mute(): void
unmute(): void
isMuted(): boolean

Global on/off switch for the master output.

SOUND_FAMILIES / SOUND_INSTANCES

Exported readonly arrays for building your own picker UI.

SoundFamily and SoundInstance are also exported as types — the same values, at compile time — for keeping your own lookup maps honest:

app.ts
import { setFamily, type SoundFamily } from 'chordal';

const familyForTheme: Record<'light' | 'dark', SoundFamily> = {
  light: 'glass-crystal',
  dark: 'metallic-tact',
};

setFamily(familyForTheme[theme]);

Families

Instances

Playground

Embed <chordal-playground> for an interactive family/instance browser. It's a dev-time tool, not for production pages.

AGENTS.md

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 ↗