skills/app-sound-design/SKILL.md
Design decisions for when and what sounds to use in web apps, mobile apps, and desktop native apps — plus how to create them with AI. Covers UI sound vocabulary (confirms, alerts, transitions, errors), platform conventions (iOS, macOS, Material), creating sounds with AI tools (ElevenLabs, AudioCraft, Stable Audio), Web Audio API for procedural sounds, haptic-audio pairing, and the psychology of satisfying vs. annoying UI sounds. Activate on 'app sounds', 'UI sounds', 'notification sound', 'sound design app', 'earcons', 'sonic branding', 'interaction sounds', 'feedback sounds', 'app audio', 'satisfying sounds'. NOT for professional audio engineering (use sound-engineer), Windows 3.1 retro sounds specifically (use win31-audio-design), music composition (use sound-engineer).
npx skillsauth add curiositech/windags-skills app-sound-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Makes the design decisions about when, what, and how sounds should appear in applications. This is not about audio engineering (that is sound-engineer) or retro sound effects (that is win31-audio-design). This skill answers the hardest question in UI sound: should this interaction make a noise at all? And if yes, what kind?
Use for:
Do NOT use for:
Does the interaction need FEEDBACK?
|
+-- User initiated action (tap, click, submit)?
| +-- Is the result visible on screen?
| | +-- YES and obvious --> Sound is OPTIONAL (often skip)
| | +-- YES but subtle --> Sound HELPS (gentle confirm)
| | +-- NO (background process) --> Sound RECOMMENDED
| +-- Is the action destructive?
| +-- YES --> Sound RECOMMENDED (distinct warning tone)
| +-- NO --> Sound OPTIONAL
|
+-- System initiated event (notification, alert)?
| +-- Is the user in the app?
| | +-- YES --> Subtle in-app sound
| | +-- NO --> Notification sound (follow OS conventions)
| +-- Is it time-sensitive?
| +-- YES --> More urgent/attention-grabbing sound
| +-- NO --> Can be silent or ambient
|
+-- Ambient/atmospheric (background state)?
+-- Does the app benefit from presence? --> Subtle ambient
+-- Is the user working/focused? --> SILENCE
Every application needs sounds from these categories. Not all categories are required — most apps need 3-5.
| Sound Type | Purpose | Character | Duration | Example | |-----------|---------|-----------|----------|---------| | Confirm | Action succeeded | Bright, rising pitch | 100-300ms | Slack message sent | | Error | Action failed | Low, flat or descending | 200-400ms | Form validation fail | | Notification | New information arrived | Distinctive, medium energy | 300-600ms | Slack incoming message |
| Sound Type | Purpose | Character | Duration | Example | |-----------|---------|-----------|----------|---------| | Transition | Navigation between views | Subtle whoosh or tap | 50-150ms | iOS page turn | | Toggle | Binary state change | Crisp click or snap | 50-100ms | Switch on/off | | Hover | Mouse-over feedback | Barely audible tick | 20-50ms | Menu item hover | | Selection | Item chosen from list | Soft pop or pluck | 50-150ms | Dropdown select | | Delete | Destructive removal | Hollow thud or swoosh | 150-300ms | Trash action |
| Sound Type | Purpose | Character | Duration | Example | |-----------|---------|-----------|----------|---------| | Achievement | Milestone reached | Musical, celebratory | 500ms-2s | Duolingo lesson complete | | Ambient | Background presence | Looping, atmospheric | Continuous | Noisli, Forest app | | Brand Sting | App open/close | Iconic, memorable | 500ms-1.5s | Netflix ta-dum | | Easter Egg | Hidden delight | Playful, unexpected | Variable | Konami code reward |
Apple HIG mandates specific audio behaviors:
ambient — mixes with other audio, respects silent switchsoloAmbient — silences other audio, respects silent switchplayback — ignores silent switch (for media apps ONLY)UIImpactFeedbackGenerator alongside audioApple's Sound Characteristics:
// iOS: Play a system sound
import AudioToolbox
AudioServicesPlaySystemSound(1057) // Standard keyboard click
// iOS: Haptic + custom sound
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
// Play your custom sound simultaneously
Google's sound design principles:
Material Sound Characteristics:
Web has the WEAKEST sound conventions, which means more freedom but also more risk of annoyance.
Best for: Realistic, high-quality UI sounds with specific character descriptions.
# Generate a confirmation chime
curl -X POST "https://api.elevenlabs.io/v1/sound-generation" \
-H "xi-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Short bright upward chime, digital, clean sine wave, 200ms, UI confirmation sound",
"duration_seconds": 0.5
}'
Effective prompts for ElevenLabs:
Best for: Developers who want full control and no API costs.
from audiocraft.models import AudioGen
from audiocraft.data.audio import audio_write
model = AudioGen.get_pretrained('facebook/audiogen-medium')
model.set_generation_params(duration=0.5)
# Generate UI sounds
descriptions = [
"short digital confirmation beep, ascending pitch, clean",
"low error buzz, brief, not harsh, digital",
"gentle notification chime, two notes, pleasant",
]
wav = model.generate(descriptions)
for i, w in enumerate(wav):
audio_write(f'ui_sound_{i}', w.cpu(), model.sample_rate)
Best for: Longer ambient sounds, background textures, brand stings.
import torch
from stable_audio_tools import get_pretrained_model
from stable_audio_tools.inference.generation import generate_diffusion_cond
model, model_config = get_pretrained_model("stabilityai/stable-audio-open-1.0")
# Generate ambient background
conditioning = [{
"prompt": "calm ambient digital texture, warm low hum, suitable for productivity app background, loopable",
"seconds_total": 10
}]
AI-generated sounds need cleanup before shipping:
For web apps, generating sounds procedurally eliminates the need for audio files entirely. Smaller bundles, instant availability.
class UISound {
private ctx: AudioContext | null = null;
private getContext(): AudioContext {
if (!this.ctx) {
this.ctx = new AudioContext();
}
return this.ctx;
}
/** Bright ascending confirmation */
confirm(): void {
const ctx = this.getContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(600, ctx.currentTime);
osc.frequency.linearRampToValueAtTime(900, ctx.currentTime + 0.15);
gain.gain.setValueAtTime(0.15, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.2);
}
/** Low descending error tone */
error(): void {
const ctx = this.getContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'triangle';
osc.frequency.setValueAtTime(400, ctx.currentTime);
osc.frequency.linearRampToValueAtTime(200, ctx.currentTime + 0.3);
gain.gain.setValueAtTime(0.12, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.35);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.35);
}
/** Subtle click for toggles and selections */
click(): void {
const ctx = this.getContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'square';
osc.frequency.setValueAtTime(800, ctx.currentTime);
gain.gain.setValueAtTime(0.08, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.05);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.05);
}
/** Three-note notification melody */
notify(): void {
const ctx = this.getContext();
const notes = [523, 659, 784]; // C5, E5, G5
notes.forEach((freq, i) => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, ctx.currentTime);
const start = ctx.currentTime + i * 0.12;
gain.gain.setValueAtTime(0, start);
gain.gain.linearRampToValueAtTime(0.12, start + 0.02);
gain.gain.exponentialRampToValueAtTime(0.001, start + 0.15);
osc.start(start);
osc.stop(start + 0.15);
});
}
}
// Usage
const sound = new UISound();
document.querySelector('#submit')?.addEventListener('click', () => sound.confirm());
On mobile, haptics and audio should be designed as a UNIT. The haptic arrives first (immediate physical feedback), the sound arrives second (confirmation and character).
| Interaction | Haptic | Sound | Timing | |------------|--------|-------|--------| | Tap button | Light impact | Short click | Simultaneous | | Toggle switch | Medium impact | Crisp snap | Haptic 10ms before sound | | Pull-to-refresh | Selection tick during pull; notification on complete | Subtle stretch sound; confirm chime | Haptic leads by 20ms | | Delete swipe | Heavy impact at threshold | Hollow swoosh | Haptic at commit point | | Long press | Rigid continuous | Low building hum | Haptic starts with sound | | Success | Success pattern (triple tap) | Ascending arpeggio | Aligned to beat |
import UIKit
import AVFoundation
class FeedbackManager {
static let shared = FeedbackManager()
private let lightImpact = UIImpactFeedbackGenerator(style: .light)
private let mediumImpact = UIImpactFeedbackGenerator(style: .medium)
private let heavyImpact = UIImpactFeedbackGenerator(style: .heavy)
private let notificationFeedback = UINotificationFeedbackGenerator()
func tapConfirm() {
lightImpact.impactOccurred()
SoundPlayer.play(.confirm)
}
func errorShake() {
notificationFeedback.notificationOccurred(.error)
SoundPlayer.play(.error)
}
func deleteCommit() {
heavyImpact.impactOccurred()
SoundPlayer.play(.delete)
}
}
Why some UI sounds feel "right" and others feel "cheap":
| Property | Why It Works | Example | |----------|-------------|---------| | Pitch rises | Signals progress, optimism, completion | Confirm chimes go UP | | Short decay | Feels responsive and controlled | Nintendo menu sounds | | Harmonic richness | Feels organic and warm | Slack's "knock" sound | | Consistent volume | Feels professional and predictable | Apple system sounds | | Rhythmic alignment | Feels intentional and designed | Duolingo's streak counter |
| Property | Why It Grates | Avoid | |----------|--------------|-------| | Repetitive without variation | Humans habituate, then are irritated | Same exact sound 100x/day | | Too loud relative to context | Breaks concentration, startles | Notification sounds above -12dB | | Pure sine at high frequency | Perceived as alarm, triggers anxiety | Any sound above 2kHz pure tone | | Long sustained notes | Feels like something is wrong | Tones lasting >500ms for routine feedback | | Dissonant intervals | Creates unconscious discomfort | Minor seconds or tritones in notifications |
Any sound heard more than ~15 times per hour becomes irritating regardless of how pleasant it initially was. Solutions:
// Subtle pitch variation to prevent habituation
function playWithVariation(baseFrequency: number): void {
const variation = 1 + (Math.random() - 0.5) * 0.1; // +/- 5%
const frequency = baseFrequency * variation;
// ... use this frequency in your oscillator
}
| App Type | Confirm | Error | Notification | Transition | Ambient | Brand Sting | |----------|---------|-------|-------------|------------|---------|-------------| | Productivity (Notion) | Optional | Yes | Yes | Skip | Skip | Skip | | Social (Slack) | Yes | Yes | Yes | Skip | Skip | Skip | | Gaming | Yes | Yes | Yes | Yes | Yes | Yes | | E-commerce | Skip | Yes | Yes | Skip | Skip | Optional | | Creative tools | Yes | Yes | Optional | Yes | Optional | Optional | | Health/meditation | Skip | Skip | Gentle | Yes | Yes | Yes | | Finance/banking | Skip | Yes | Yes | Skip | Skip | Skip | | Children's apps | Yes | Yes | Yes | Yes | Optional | Yes |
Playing a sound on every button click, link tap, and scroll event creates an overwhelming audio assault. Reserve sounds for MEANINGFUL state changes, not routine navigation.
Web apps that play ambient sound without user consent will be immediately closed. Always require explicit opt-in. Browsers actively block autoplay audio for good reason.
Playing a typewriter click sound in a modern chat app creates cognitive dissonance unless the entire aesthetic is retro. Sound must match the visual design language.
Every app with sound MUST have an easily discoverable mute control. Burying it 3 menus deep is nearly as bad as having no mute at all.
Using the same chime for "message sent" and "message received" destroys the information value of sound. Each semantic category must have a distinct sound.
Playing notification sounds while a user is in a full-screen editor, reading an article, or watching a video. Respect focus state and suppress non-critical audio.
A beautiful sound designed on studio monitors may be a harsh buzz on phone speakers. Always test on the cheapest speakers your users will have: phone speakers cut below 200Hz and boost 1-4kHz.
prefers-reduced-motion considered for audio (some users want minimal sensory input)tools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.