[{"data":1,"prerenderedAt":336},["ShallowReactive",2],{"writing-raw-d-\u002Fwriting\u002Fwe-shipped-web-have-sounds":3,"writing-doc-d-\u002Fwriting\u002Fwe-shipped-web-have-sounds":216},{"id":4,"title":5,"author":6,"body":7,"date":207,"description":208,"extension":209,"heroMedia":210,"link":210,"meta":211,"navigation":88,"path":212,"seo":213,"status":210,"stem":214,"version":210,"__hash__":215},"content\u002Fwriting\u002Fwe-shipped-web-have-sounds.md","We Shipped web-have-sounds","TheAlphaOnes",{"type":8,"value":9,"toc":201},"minimark",[10,19,24,27,34,37,41,46,71,108,111,115,158,161,165,168,171,188,197],[11,12,13,14,18],"p",{},"A few weeks ago, we quietly put ",[15,16,17],"code",{},"@thenormvg\u002Fweb-have-sounds"," on npm. No launch post, no product hunt, just pushed it and started using it ourselves. We're writing this now because we think it's worth explaining — both what it does and why we built it.",[20,21,23],"h2",{"id":22},"the-friction","The Friction",[11,25,26],{},"We'd been building UIs for a while, and there was always this moment in the process where you'd want to add a small audio cue — a click when you press a button, a soft chime when something completes, a subtle drop when a modal closes — and the process of actually doing it felt disproportionately annoying.",[11,28,29,30,33],{},"Your options were roughly: load a ",[15,31,32],{},".wav"," file (suddenly your bundle has audio assets and a loading problem), use a third-party library (dependencies and size), or dig into the Web Audio API yourself (which is powerful and also genuinely not fun to work with for simple use cases).",[11,35,36],{},"We wanted none of those options. We wanted to write one line of code and hear a sound.",[20,38,40],{"id":39},"what-we-built","What We Built",[11,42,43,45],{},[15,44,17],{}," generates UI sounds through mathematics. No audio files. No network requests. No dependencies. It uses the Web Audio API — which is already in your browser — to synthesize sound in real time, exactly when you call it.",[47,48,53],"pre",{"className":49,"code":50,"language":51,"meta":52,"style":52},"language-bash shiki shiki-themes github-light github-dark","npm install @thenormvg\u002Fweb-have-sounds\n","bash","",[15,54,55],{"__ignoreMap":52},[56,57,60,64,68],"span",{"class":58,"line":59},"line",1,[56,61,63],{"class":62},"sScJk","npm",[56,65,67],{"class":66},"sZZnC"," install",[56,69,70],{"class":66}," @thenormvg\u002Fweb-have-sounds\n",[47,72,76],{"className":73,"code":74,"language":75,"meta":52,"style":52},"language-javascript shiki shiki-themes github-light github-dark","import { playUISound } from '@thenormvg\u002Fweb-have-sounds';\n\nbutton.addEventListener('click', () => {\n  playUISound('click');\n});\n","javascript",[15,77,78,83,90,96,102],{"__ignoreMap":52},[56,79,80],{"class":58,"line":59},[56,81,82],{},"import { playUISound } from '@thenormvg\u002Fweb-have-sounds';\n",[56,84,86],{"class":58,"line":85},2,[56,87,89],{"emptyLinePlaceholder":88},true,"\n",[56,91,93],{"class":58,"line":92},3,[56,94,95],{},"button.addEventListener('click', () => {\n",[56,97,99],{"class":58,"line":98},4,[56,100,101],{},"  playUISound('click');\n",[56,103,105],{"class":58,"line":104},5,[56,106,107],{},"});\n",[11,109,110],{},"That's it. The sound plays instantly, because it's generated at call time rather than loaded from somewhere.",[20,112,114],{"id":113},"what-it-can-do","What It Can Do",[11,116,117,118,121,122,121,125,121,128,121,131,121,134,121,137,121,140,143,144,147,148,121,151,121,154,157],{},"Nine sound types, nine sonic themes. You can use ",[15,119,120],{},"click",", ",[15,123,124],{},"pop",[15,126,127],{},"toggle",[15,129,130],{},"tick",[15,132,133],{},"drop",[15,135,136],{},"success",[15,138,139],{},"error",[15,141,142],{},"whoosh",", or ",[15,145,146],{},"notification"," — each tuned for specific UI contexts. And you can switch themes like ",[15,149,150],{},"aero",[15,152,153],{},"arcade",[15,155,156],{},"glass",", and more to match the personality of whatever you're building.",[11,159,160],{},"If you want to go deeper, everything is parameterizable. You can pass your own mathematical parameters and sculpt something entirely custom. But the defaults are already pretty good.",[20,162,164],{"id":163},"why-open-source","Why Open Source",[11,166,167],{},"We build open source because the tools we rely on most are open source. It feels right to put something back.",[11,169,170],{},"We also think this specific library fills a gap that nobody else has filled particularly well. Sound in web apps is underused, partly because the tooling is bad. Maybe this makes it a little easier for someone who would have otherwise skipped it.",[11,172,173,174,181,182,187],{},"The ",[175,176,180],"a",{"href":177,"rel":178},"https:\u002F\u002Fgithub.com\u002FNormVg\u002Fweb-have-sound",[179],"nofollow","GitHub is here",". The ",[175,183,186],{"href":184,"rel":185},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@thenormvg\u002Fweb-have-sounds",[179],"npm package is here",". Issues and PRs welcome.",[11,189,190,191,196],{},"If you build something with it, tell us on ",[175,192,195],{"href":193,"rel":194},"https:\u002F\u002Ftwitter.com\u002FTheNormVg",[179],"X (Twitter)",". We're curious what people make.",[198,199,200],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":52,"searchDepth":85,"depth":85,"links":202},[203,204,205,206],{"id":22,"depth":85,"text":23},{"id":39,"depth":85,"text":40},{"id":113,"depth":85,"text":114},{"id":163,"depth":85,"text":164},"21.07.2026","A few weeks ago, we quietly put @thenormvg\u002Fweb-have-sounds on npm. No launch post, no product hunt, just pushed it and started using it ourselves. We're writing this now because we think it's worth explaining — both what it does and why we built it.","md",null,{},"\u002Fwriting\u002Fwe-shipped-web-have-sounds",{"title":5,"description":208},"writing\u002Fwe-shipped-web-have-sounds","IeJS7PWg0s_EaPJIeCeBPxZATNqB7BzTa1OtlXUtidA",{"id":4,"title":5,"author":6,"body":217,"date":207,"description":208,"extension":209,"heroMedia":-1,"link":-1,"meta":334,"navigation":88,"path":212,"seo":335,"status":-1,"stem":214,"version":-1,"__hash__":215,"homeImage":-1,"tickerTop":-1,"tickerBottom":-1},{"type":8,"value":218,"toc":328},[219,223,225,227,231,233,235,239,251,275,277,279,305,307,309,311,313,321,326],[11,220,13,221,18],{},[15,222,17],{},[20,224,23],{"id":22},[11,226,26],{},[11,228,29,229,33],{},[15,230,32],{},[11,232,36],{},[20,234,40],{"id":39},[11,236,237,45],{},[15,238,17],{},[47,240,241],{"className":49,"code":50,"language":51,"meta":52,"style":52},[15,242,243],{"__ignoreMap":52},[56,244,245,247,249],{"class":58,"line":59},[56,246,63],{"class":62},[56,248,67],{"class":66},[56,250,70],{"class":66},[47,252,253],{"className":73,"code":74,"language":75,"meta":52,"style":52},[15,254,255,259,263,267,271],{"__ignoreMap":52},[56,256,257],{"class":58,"line":59},[56,258,82],{},[56,260,261],{"class":58,"line":85},[56,262,89],{"emptyLinePlaceholder":88},[56,264,265],{"class":58,"line":92},[56,266,95],{},[56,268,269],{"class":58,"line":98},[56,270,101],{},[56,272,273],{"class":58,"line":104},[56,274,107],{},[11,276,110],{},[20,278,114],{"id":113},[11,280,117,281,121,283,121,285,121,287,121,289,121,291,121,293,121,295,143,297,147,299,121,301,121,303,157],{},[15,282,120],{},[15,284,124],{},[15,286,127],{},[15,288,130],{},[15,290,133],{},[15,292,136],{},[15,294,139],{},[15,296,142],{},[15,298,146],{},[15,300,150],{},[15,302,153],{},[15,304,156],{},[11,306,160],{},[20,308,164],{"id":163},[11,310,167],{},[11,312,170],{},[11,314,173,315,181,318,187],{},[175,316,180],{"href":177,"rel":317},[179],[175,319,186],{"href":184,"rel":320},[179],[11,322,190,323,196],{},[175,324,195],{"href":193,"rel":325},[179],[198,327,200],{},{"title":52,"searchDepth":85,"depth":85,"links":329},[330,331,332,333],{"id":22,"depth":85,"text":23},{"id":39,"depth":85,"text":40},{"id":113,"depth":85,"text":114},{"id":163,"depth":85,"text":164},{},{"title":5,"description":208},1784632984610]