# Sound Only for Significant Events

**NEVER** · **ID:** `interactions-sound-only-for-significant-events` · **Category:** interactions
**Source:** Custom
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/interactions-sound-only-for-significant-events

> NEVER: Attach sound to high-frequency interactions — typing, keyboard navigation, hover, scroll. Reserve it for confirmations the user needs assurance of (payments, uploads, form submissions) and for errors/warnings that must not be overlooked. A cue that fires on every input stops being feedback and trains the user to tune out every other sound you make.

Confirmations and errors earn a sound; typing, hover, scroll and navigation never do

Cherry-picked from Raphael Salaja's sounds-on-the-web skill, merging `appropriate-no-high-frequency` with `appropriate-confirmations-only` and `appropriate-errors-warnings`. Frequency is the whole test. A sound on a payment confirmation fires once and reassures; the same sound on a keystroke fires eighty times a minute and becomes noise the user learns to tune out — taking every other sound in your product down with it, including the error you actually needed them to hear. The skill's own appropriateness matrix draws the line explicitly: payment success yes (significant confirmation), form submission yes (user needs assurance), error yes (can't be overlooked), notification yes (may not be looking at the screen), button click maybe (only for significant buttons), typing no (too frequent), hover no (decorative only), scroll no (too frequent), navigation no (keyboard nav would be noisy). This is the same shape as "Match Motion to Frequency", which withholds animation from keyboard-initiated and high-frequency interactions for exactly the same reason: feedback that fires on every input stops being feedback and becomes texture.

## Bad — do not do this

`interactions-sound-only-for-significant-events-bad`

```tsx
import { useState } from 'react';
import { playBlip } from '@/components/demo-kit/playBlip';

/**
 * A blip on every keystroke. Typing is the highest-frequency interaction in any
 * interface — so this fires dozens of times a minute, becomes noise, and trains
 * the user to tune out every other sound the product makes, including the errors.
 */
export function SoundOnlyForSignificantEventsBad() {
  const [soundEnabled, setSoundEnabled] = useState(false);
  const [value, setValue] = useState('');
  const [blips, setBlips] = useState(0);

  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-4">
        <label className="mb-4 flex items-center gap-2 text-xs text-muted-foreground">
          <input
            type="checkbox"
            checked={soundEnabled}
            onChange={(e) => setSoundEnabled(e.target.checked)}
            className="size-4 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
          />
          Enable sound for this demo (off by default — nothing autoplays)
        </label>

        <div className="rounded-md border border-border bg-muted p-4">
          <label htmlFor="noisy-input" className="block text-xs text-muted-foreground mb-1">
            Search
          </label>
          <input
            id="noisy-input"
            value={value}
            onChange={(e) => {
              // Fires on every single character. Sound as texture, not as information.
              if (soundEnabled) playBlip();
              setBlips((n) => n + 1);
              setValue(e.target.value);
            }}
            placeholder="Type a sentence here…"
            className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
          />
        </div>

        <p className="mt-3 text-xs text-muted-foreground">
          Type a sentence. Every keystroke blips. Nothing was confirmed, nothing failed, nothing needed your
          attention — the sound carries no information at all, and by the tenth character you have stopped
          hearing it.
        </p>

        <div className="mt-3 rounded-md border border-dashed border-border p-2">
          <p className="text-xs text-muted-foreground">Audio monitor — instrumentation, not product UI:</p>
          <p className="mt-1 text-xs font-mono text-destructive" aria-live="polite">
            {blips === 0
              ? 'no keystrokes yet'
              : `${blips} blip${blips > 1 ? 's' : ''} ${soundEnabled ? 'played' : 'suppressed (sound off)'} for ${blips} keystroke${blips > 1 ? 's' : ''} — a 1:1 ratio with typing`}
          </p>
        </div>
      </div>
      <p className="text-xs text-destructive mt-4">
        Sound on a high-frequency interaction — it becomes noise, and takes the useful sounds down with it
      </p>
    </div>
  );
}
```

## Good — do this

`interactions-sound-only-for-significant-events-good`

```tsx
import { useState } from 'react';
import { playBlip } from '@/components/demo-kit/playBlip';

/**
 * Silence while typing. One blip on submit — a confirmation, which is exactly the
 * category the appropriateness matrix says earns a sound. Because it fires once per
 * task instead of once per character, it still means something when it fires.
 */
export function SoundOnlyForSignificantEventsGood() {
  const [soundEnabled, setSoundEnabled] = useState(false);
  const [value, setValue] = useState('');
  const [keystrokes, setKeystrokes] = useState(0);
  const [blips, setBlips] = useState(0);
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (soundEnabled) playBlip();
    setBlips((n) => n + 1);
    setSubmitted(true); // Visual channel first — sound only ever supplements it.
  };

  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-4">
        <label className="mb-4 flex items-center gap-2 text-xs text-muted-foreground">
          <input
            type="checkbox"
            checked={soundEnabled}
            onChange={(e) => setSoundEnabled(e.target.checked)}
            className="size-4 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
          />
          Enable sound for this demo (off by default — nothing autoplays)
        </label>

        <form onSubmit={handleSubmit} className="rounded-md border border-border bg-muted p-4">
          <label htmlFor="quiet-input" className="block text-xs text-muted-foreground mb-1">
            Search
          </label>
          <input
            id="quiet-input"
            value={value}
            onChange={(e) => {
              // No sound here. Typing is high-frequency: the visual feedback is the character
              // appearing, and that is already instant and sufficient.
              setKeystrokes((n) => n + 1);
              setSubmitted(false);
              setValue(e.target.value);
            }}
            placeholder="Type a sentence here…"
            className="mb-3 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
          />
          <button
            type="submit"
            className={`w-full rounded-md px-3 py-2 text-sm focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring ${
              submitted
                ? 'bg-success/15 text-success ring-1 ring-success'
                : 'bg-primary text-primary-foreground'
            }`}
          >
            {submitted ? '✓ Submitted' : 'Submit'}
          </button>
          <p role="status" aria-live="polite" className="mt-2 min-h-4 text-xs text-success">
            {submitted ? 'Search saved.' : ''}
          </p>
        </form>

        <p className="mt-3 text-xs text-muted-foreground">
          Type as much as you like — silence. Submit once — one blip. The matrix draws the line by
          frequency: payment success, form submission, errors and notifications get a sound; typing, hover,
          scroll and keyboard navigation never do.
        </p>

        <div className="mt-3 rounded-md border border-dashed border-border p-2">
          <p className="text-xs text-muted-foreground">Audio monitor — instrumentation, not product UI:</p>
          <p className="mt-1 text-xs font-mono text-success" aria-live="polite">
            {keystrokes} keystroke{keystrokes === 1 ? '' : 's'} → 0 blips ·{' '}
            {blips === 0
              ? 'no submissions yet'
              : `${blips} submission${blips > 1 ? 's' : ''} → ${blips} blip${blips > 1 ? 's' : ''} ${soundEnabled ? 'played' : 'suppressed (sound off)'}`}
          </p>
        </div>
      </div>
      <p className="text-xs text-success mt-4">
        Silent while typing, one confirmation blip on submit — the sound still means something
      </p>
    </div>
  );
}
```

## References

- [Raphael Salaja: sounds-on-the-web SKILL.md](https://github.com/raphaelsalaja/skill/blob/main/skills/sounds-on-the-web/SKILL.md)
- [MDN: Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)
