npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

chordal

v0.1.0

Published

A tunable, zero-audio-file sonic design system for the web, powered by the Web Audio API.

Readme

chordal

A tunable, zero-audio-file sonic design system for the web, powered by the Web Audio API.

Nine sound families, ten interaction shapes each: nine discrete sounds plus continuous drag input, synthesized live. No audio files, no downloads, nothing to configure. ESM-only, zero runtime dependencies, safe to import during SSR: nothing plays until a real user gesture triggers it in a browser.

Install

npm install chordal

Quick start

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

<button data-sound-hover data-sound-click>Save</button>
import { bind } from 'chordal';

bind();

Only using one family?

Importing chordal pulls in all nine families (~6.9KB gzipped, everything included). If your app only ever uses one, import that family directly and pull in createPlayer from chordal/lite instead, for a real, measured, ~4.2KB gzipped, about 39% smaller since the other eight families' data is never bundled at all, not just hidden behind a runtime check.

import chime from 'chordal/chime';
import { createPlayer } from 'chordal/lite';

const { play, bind, playContinuous } = createPlayer(chime);

play('hover');
bind(); // same data-sound-* attribute scanning as the main API

Every family is available as its own subpath: chordal/soft-bubble, chordal/glass-crystal, chordal/paper-snap, chordal/metallic-tact, chordal/chime, chordal/digital-blip, chordal/spring, chordal/tiny-sparkle, chordal/snap.

One real tradeoff: bind() from createPlayer() has no per-element family override (data-sound-click="chime") — there's only ever the one family loaded, so there's nothing to switch to. Reach for the main chordal import if you need that.

Attributes

| 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 |

Leave the attribute value empty to use the active family, or set it to any family name (data-sound-click="chime") to override just that element.

API

play(instance, options?)

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

The direct, imperative trigger. Call it from your own event handlers or async callbacks. options overrides family, volume, pitch, length, or tone for just this call, plus state: 'on' | 'off' for the toggle instance.

import { play } from 'chordal';

play('success', { family: 'glass-crystal' });

bind(root?)

bind(root?: ParentNode): void

Scans root (defaults to document) for data-sound-* attributes and wires up the matching listeners in one pass. Safe to call again after DOM changes: already-bound elements are skipped, never double-bound.

playContinuous(action, valueRatio, options?)

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

For continuous drag interactions like a range slider. Call it on every input event with the current 0-1 ratio. Debounced and pitch-quantized internally, so fast dragging reads as musical steps, not a siren sweep.

slider.addEventListener('input', () => {
  playContinuous('slider', Number(slider.value) / 100);
});

setFamily(family) / getFamily()

The family play() and bind() use when a call doesn't override its own. Call setFamily() once, for example from a theme/settings picker, to change your whole app's sound identity.

mute() / unmute() / isMuted()

Global on/off switch for the master output.

SOUND_FAMILIES / SOUND_INSTANCES

Exported readonly arrays for building your own family/instance picker UI without hardcoding the list.

Families

| Family | Character | |---|---| | soft-bubble | Warm, round, gentle: a soft pop with a pleasant, slightly sweet tone. | | glass-crystal | Bright and resonant, like a light tap on crystal, delicate but clear. | | paper-snap | Dry, textured noise: a papery crackle rather than a musical tone. | | metallic-tact | A mechanical, tactile click, resonant in a way that reads as hardware. | | chime | Bell-like and tonal: a clean ring that decays naturally. | | digital-blip | Squared-off and retro, clearly electronic, an 8-bit-style blip. | | spring | Bouncy and twangy, with a springy pitch character true to the name. | | tiny-sparkle | Delicate and high-pitched: light and glittery. | | snap | A percussive knock, dry and immediate. |

Instances

| Instance | Use | |---|---| | hover | A light, quick probe for pointer-enter feedback, weightless, with no reverb tail. | | click | The core tactile confirmation for a press. | | success | A short celebratory phrase for a completed action. | | toggle | A two-part mechanical snap for switching between on/off states. | | error | A descending, dissonant phrase signaling something went wrong, bound to the native invalid event. | | sent | For the moment an async action actually starts. | | notification | For when something appears without user action, like a toast. | | listening | A stateful on/off cue for a mic or voice-input session starting and stopping — pass { state: 'on' \| 'off' }, same as toggle. | | delete | An item being removed or discarded. Programmatic only — trigger it yourself via play('delete', ...); there's no data-sound-delete binding, since deletions should never fire from a raw DOM event without your own confirmation logic. | | slider | Continuous, pitch-quantized feedback while dragging a range input (playContinuous() only). |

License

MIT