Library / SDK
rexa-developer/tiks avatar
rexa-developer/tiks

tiks synthesises web UI sounds from oscillators, so no audio files ship

Procedural UI sounds for the web. Zero audio files. Pure synthesis.

626 stars9 forksTypeScriptMIT

At a glance

What is it?
rexa-developer/tiks is an MIT licensed TypeScript library that generates interface sounds at run time with the Web Audio API, using oscillators, noise buffers and gain envelopes inside one shared synthesis engine, with ten named sounds, four built-in themes, custom theme definitions and adapters for React, Vue, Svelte and Solid. The whole package is about 3.5 kilobytes gzipped, and there is not a single audio file in it.
Who is it for?
Adopt tiks if you are building a web application whose interface feels flat and you want a small set of consistent, tunable sounds without shipping or hosting audio files, since the synthesis approach keeps the payload to a few kilobytes and lets you retune every sound from one theme object.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 47 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap is that web apps have no interface sounds

The premise in the README is a short observation about platforms. Every native app has satisfying sounds, an iOS toggle click, a macOS trash crumple, an Android keyboard tap, and web apps have nothing. tiks is the attempt to give a browser application that missing sensory layer, and the way it does it is the interesting part. Every sound is generated at run time through oscillators, noise buffers and gain envelopes. There are no audio files shipped, just math. The consequence for a web team is a bundle that does not grow when you add sounds, and no asset pipeline, no hosting of short files, and no licensing questions about where the recording came from. The second architectural point is that all sounds share a common synthesis engine with a unified theme, so they sound as if they belong together rather than as ten unrelated samples, and the README claims a gzip size of about 3.5KB for the package.

Ten sounds, each with a documented character and length

The sound list is short and each entry has a stated character and duration, which is the kind of detail that makes a UI sound set usable rather than decorative:

| Method | Character | Duration | |--------|-----------|----------| | `click()` | Short, crisp tap | ~12ms | | `toggle(true)` | Rising pitch snap | ~60ms | | `toggle(false)` | Falling pitch snap | ~60ms | | `success()` | Two-note rising chime | ~200ms | | `error()` | Gentle buzz/thud | ~150ms | | `warning()` | Cautious double-tap | ~180ms | | `hover()` | Whisper-soft tick | ~15ms | | `pop()` | Playful bubble pop | ~80ms | | `swoosh()` | Quick transition whoosh | ~120ms | | `notify()` | Bright attention ping | ~300ms |

The durations matter more than they look. A click at twelve milliseconds is a tick rather than a sound, which is what makes it usable on a button without becoming a nuisance, and a notify at three hundred milliseconds is a chime rather than a tick, which is what makes it appropriate for something that demands attention. The toggle pair is designed to be heard as a state change, rising when something is switched on and falling when it is switched off, which is a small piece of interface design that most sound sets leave to chance.

The hover sound is throttled on purpose

One entry in the table comes with its own warning, and it is the most useful thing the README says about performance. The hover sound is a high-frequency primitive and the recommendation is to use it sparingly. The engine throttles it to one tick per 80ms by default, so dense pointer movement cannot machine-gun the sound, and the throttle can be changed with init({ hoverThrottleMs }) or disabled by passing 0. Even with the throttle in place, the documentation says to reserve hover for deliberate, low-density targets, and never to bind it to dense lists, menus or whole-page hover regions. This is the failure mode a synthesiser makes easy to hit, because generating a sound costs almost nothing, so nothing stops a developer from calling it on every pointer move. The library mitigates it, and the documentation explains the mitigation, and both of those are better than a sound set that leaves you to discover the problem in production.

Four themes, and a custom one is a plain object

The theme is the single knob that changes the character of every sound, and there are four built-ins: soft, which is warm, rounded and Apple-like and is the default, crisp, which is sharp, tactile and has a mechanical keyboard feel, arcade, which is chunky 8-bit with a square-wave chiptune character, and glass, which is airy, resonant, high and ringing. A custom theme is defined with defineTheme and is then passed to init, and the theme can also be switched at run time with setTheme. The property list is short enough to hold in your head: name, baseFreq as the root frequency all sounds derive from, noiseColor for the transient noise, oscType among sine, triangle, square and sawtooth, filterFreq and filterQ for the bandpass on click transients, attack and decay in seconds and as a multiplier where 1.0 is normal and 0.5 is snappy, and brightness as a highpass cutoff. Because every sound is derived from those numbers, one object retunes the entire set, which is a much better answer than shipping a second set of samples.

The framework adapters share state, and that is the sharp edge

There are hooks for React, Vue, Svelte and Solid, and the React one is the clearest example of the pattern:

tsx
import { useTiks } from '@rexa-developer/tiks/react'

function ToggleButton() {
  const { click, toggle } = useTiks()

  return (
    <button onClick={() => {
      click()
      setEnabled(prev => {
        toggle(!prev)
        return !prev
      })
    }}>
      Toggle
    </button>
  )
}

The hook auto-initialises on mount and the returned methods are stable references. The important detail is the state model, which the README repeats for every adapter. Volume and muted are shared globally across all useTiks hooks, because they live on the underlying audio engine, while theme is per-hook, and if two hooks pass different volume values the last mounted one wins. The Svelte and Solid adapters are described as plain function wrappers that import no framework APIs, which keeps them tiny. The global state is not a bug, it is the natural consequence of one audio engine, but it is the thing to design around if your application has a settings screen and a gameplay screen that want different volumes.

Tree shaking, multiple instances, and a declarative binding

Three features make the library usable in different shapes of application. For bundle size, you can import only the sounds you need, so init, click and success, and the package marks itself as sideEffects free. For pages that need more than one voice, createTiks() builds an independent instance, which is how a page can have a crisp interface and an arcade game overlay at the same time:

ts
import { createTiks } from '@rexa-developer/tiks'

const ui = createTiks({ theme: 'crisp' })
const game = createTiks({ theme: 'arcade' })

And for markup that should not need a click handler, there is a declarative binding. Annotate elements with a data-tiks attribute and call bindTiks() once:

html
<button data-tiks="click">Save</button>
<input type="checkbox" data-tiks="toggle">

The supported values are click, success, error, warning, hover, pop, swoosh, notify, toggle, toggle-on and toggle-off, and unknown values are silently ignored, which is forgiving in the right way. The same module loads from a CDN through esm.sh, so a page can import it in a script tag without a build step at all.

Where synthesised sound is the wrong answer

The alternative approach is the obvious one: record or download a set of short sounds, ship them as audio files and play them with the audio element or the Web Audio API. The difference in approach is what you gain and lose. Recorded files can be anything a human or a studio produced, a real click, a spoken word, a musical sting, and that range is unreachable by synthesis. What they cost is payload, hosting, cache headers and the licensing of the recordings, plus the fact that a set of files is a fixed set, so a second theme means a second set of files. Synthesis gives you a few kilobytes of code, infinite variation from nine numbers, and a coherent set by construction, at the price of being limited to what oscillators and noise can evoke. There is also a behavioural limitation to plan for. The AudioContext is created on the first real user gesture so that it never triggers an autoplay policy warning, which means the library stays silent until the user interacts with the page, and the respectReducedMotion option, on by default, can stop sounds from playing for users who ask for reduced motion.

Editorial conclusion

Adopt tiks if you are building a web application whose interface feels flat and you want a small set of consistent, tunable sounds without shipping or hosting audio files, since the synthesis approach keeps the payload to a few kilobytes and lets you retune every sound from one theme object. Do not adopt it expecting a sound library, because there is no recorded material and no speech, and check the shared state behaviour first, since volume and muted are global while theme is per instance. Before you ship, read the hover caution, because the primitive is throttled to one tick per 80 milliseconds and dense hover regions are the failure case the documentation warns about.

Frequently asked questions

How do I install and start using tiks?

Install the package with npm install @rexa-developer/tiks, then call tiks.init() and play sounds with calls such as tiks.click(), tiks.success() and tiks.toggle(true). The README notes that the AudioContext is created on the first real user gesture, so initialising never triggers an autoplay policy warning.

Does tiks ship any audio files?

No. Every sound is generated at run time through oscillators, noise buffers and gain envelopes inside a common synthesis engine, and the README states that no audio files are shipped, with a gzip size of about 3.5KB for the package.

What sounds does tiks provide and how long are they?

Ten of them with documented characters and durations: click at about 12ms, toggle rising or falling at about 60ms, success at about 200ms, error at about 150ms, warning at about 180ms, hover at about 15ms, pop at about 80ms, swoosh at about 120ms and notify at about 300ms.

How do I change the sound theme in tiks?

Pass one of the four built-in themes to init, such as soft, crisp, arcade or glass, switch at run time with setTheme, or define your own with defineTheme using properties such as baseFreq, oscType and decay, then pass that object to init.

Is the hover sound safe to use in tiks?

Use it sparingly. The engine throttles it to one tick per 80ms by default so dense pointer movement cannot machine-gun the sound, and the throttle is adjustable with init({ hoverThrottleMs }) or can be disabled with 0. The README still says never to bind it to dense lists, menus or whole-page hover regions.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. rexa-developer/tiks on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rexa-developer-tiks.svg)](https://hysenlabs.com/projects/rexa-developer-tiks)

Was this useful?