Atmosphere✨ Premium

Typing Sound Input

Text input with a synthetic mechanical click on each keystroke: square oscillator 300–500 Hz via Web Audio API, CSS progress bar — zero dependency, zero canvas.

Web AudioInputKeyboard

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →

3 usage examples

Hero / Landing — Newsletter sign-up with audio feedback — Typing Sound Input example 1

① Hero / Landing — Newsletter sign-up with audio feedback

WhenOn a landing page whose sole CTA is email capture — the user types their address in the hero section.
WhyThe mechanical click on each keystroke creates a premium, physical feel and increases engagement. The progress bar guides users toward the right format (character count target).
SettingsRandom frequency 300–500 Hz, 'square' wave, duration 50 ms, gain 0.02 — bar at 100% = 20 chars (ideal for a short email). Change to * 3 to target 33 chars.
Full-screen retro terminal — every keystroke sounds — Typing Sound Input example 2

② Full-screen retro terminal — every keystroke sounds

WhenIn an in-browser terminal, code editor, or command generator, the user types a command or plain text across the full viewport.
WhyAudio feedback is the natural, expected behaviour of a real terminal — it creates total immersion. This is the most flattering context for the effect: large surface, visible input, sound coherent with the environment.
SettingsFrequency 200 + Math.random() * 200 (200–400 Hz) for a deeper, vintage-typewriter timbre. Wave 'square', duration 0.05 s, gain 0.02. Bar multiplier × 2 ((input.value.length + 1) * 2) to cover ~50 chars — typical command length.
Conversion micro-interaction — Message field at checkout — Typing Sound Input example 3

③ Conversion micro-interaction — Message field at checkout

WhenAt the final step of a purchase funnel or registration, the user can add a personalization note or message.
WhyThe typing sound anchors the writing act in the physical and reduces abandonment at the last step. Progress toward 100% nudges users to complete the field before moving on.
SettingsExtend duration to 0.08 s and gain to 0.03 for a slightly fuller, more deliberate sound. Bar color: replace #6366f1 with your brand primary.

How it works

On each keydown event, a square OscillatorNode is instantiated through the Web Audio API. The frequency is drawn randomly between 300 and 500 Hz (300 + Math.random() * 200) to vary the timbre slightly between consecutive keystrokes, mimicking the variability of a real keyboard. The total sound duration is 50 ms.

The volume follows an exponential envelope: the GainNode starts at 0.02, then exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.05) brings it near-silence in 50 ms. This profile — instant attack, fast decay — produces the percussive click of a mechanical key without lingering resonance. The oscillator is stopped and released automatically at t+50 ms (stop()), with no memory leak.

The AudioContext is created as a lazy singleton (getSndCtx()) with a webkitAudioContext fallback for Safari ≤ 14. It stays suspended until the first keydown — compliant with the user gesture requirement enforced by Chrome 71+ and Safari 12.1+.

The progress bar (.snd-typing-bar) is a <div> whose width is recalculated in pure CSS on each keystroke: Math.min(100, (input.value.length + 1) × 5) + '%'. It reaches 100% at 20 characters. The CSS property transition: width .1s smooths the animation without RAF or extra JS.

Accessibility

  • prefers-reduced-motion: absent from the code. The CSS bar (transition: width .1s) is not suppressed when the OS enables reduced motion — add @media (prefers-reduced-motion: reduce) { .snd-typing-bar { transition: none } } to comply with WCAG 2.3.3.
  • Sound is not triggered automatically: the AudioContext only starts after the first keypress. No unexpected noise on page load — compliant with WCAG 1.4.2 (audio control).
  • The <input> is natively keyboard-focusable via Tab. However, no <label> is associated with it in the provided code — only the placeholder acts as a label, which is a screen-reader gap (WCAG 1.3.1). Add a <label for="sndTypingInput"> or aria-label in integration.
  • The .snd-typing-indicator bar carries no role="progressbar", aria-valuenow, or aria-valuemax — progress is not communicated to screen readers. Add these attributes if the metric carries semantic value for the user.
  • Text contrast: the input on #0a0a0f background with #fff color reaches ≥ 15:1 (WCAG AA and AAA). The placeholder at ~70% white opacity sits around 10:1 — still AA-compliant.

Browser compatibility

Requires Web Audio API — available in all modern browsers since 2014. Zero external dependency, zero canvas.

Chrome 66+✓ Full
Firefox 76+✓ Full
Safari 14+✓ Full
Edge 79+✓ Full
Mobile iOS✓ Gesture req.
Android Chrome✓ Full

If both <code>AudioContext</code> and <code>webkitAudioContext</code> are absent (IE 11, Opera Mini), <code>playTone</code> throws silently (the oscillator creation block is never reached). The text input and progress bar remain fully functional — only the sound is missing.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="snd-typing-container">
  <input
    type="text"
    class="snd-typing-input"
    id="sndTypingInput"
    placeholder="Tapez quelque chose..."
  >
  <div class="snd-typing-indicator">
    <div class="snd-typing-bar" id="sndTypingBar"></div>
  </div>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Frequency range 300–500 Hz In the keydown listener, edit 300 + Math.random() * 200. Example: 200 + Math.random() * 100 gives a deeper sound (vintage typewriter); 500 + Math.random() * 300 gives a higher pitch (chiclet keyboard).
Oscillator wave type 'square' 3rd argument of playTone(freq, 0.05, 'square', 0.02). 'sine' = soft and round, 'triangle' = neutral, 'sawtooth' = more electronic and bright. 'square' is closest to a mechanical relay timbre.
Sound duration 0.05 s (50 ms) 2nd argument of playTone. 0.05 = dry key click, 0.12 = light piano note, 0.20 = perceptible organ timbre. Drop below 0.03 for a nearly imperceptible tick.
Volume (initial gain) 0.02 4th argument of playTone. 0.02 = subtle (ambient), 0.05 = present, 0.10 = loud. Stay under 0.08 to avoid startling users with headphones.
Bar color #6366f1 CSS background property of .snd-typing-bar. Replace with your brand primary. Gradients are supported: linear-gradient(90deg, #6366f1, #ec4899).
Bar fill rate × 5 (100% at 20 chars) In Math.min(100, (input.value.length + 1) * 5), adjust the multiplier: * 3 → 100% at 33 chars (long email), * 10 → 100% at 10 chars (PIN code).
CSS transition speed 0.1 s The transition: width .1s property on .snd-typing-bar. Use .3s ease-out for smoother fill, or none for instant display (and prefers-reduced-motion compliance).

FAQ

Chrome, Safari, and Firefox suspend the AudioContext until an explicit user gesture (click or keypress). The first keydown creates and resumes the context — the sound is absent only on that single keystroke. This is browser policy, not a bug. To work around it, call getSndCtx().resume() in a click handler on the page (e.g. a 'Enable sound' button) before the user starts typing.
The IIFE targets document.getElementById('sndTypingInput') at execution time — the DOM must be mounted. In React, wrap the initialization in useEffect(() => { /* run script */ }, []) or use a ref directly. In Vue, call it in the mounted() hook. For multiple instances, generate unique IDs per component (e.g. sndTypingInput-${uuid}) and add the event listener manually.
Yes. Replace the OscillatorNode with an AudioBufferSourceNode loaded via AudioContext.decodeAudioData(). Load the WAV/MP3 once at mount, store the AudioBuffer, and on each keystroke create a new AudioBufferSourceNode, connect it to the GainNode, and call start(). The listener structure stays identical — only the audio source changes.