Skip to content

Why Audio Autoplay is Bad for Website UX: A Lesson from HeroUI v3

I closed the browser tab within 3 seconds. The music started blaring from my speakers while I sat in a quiet coffee shop, and everyone turned to look at me. That was my first experience with the HeroUI v3 documentation page.

Turns out, I wasn’t alone.

The HeroUI v3 Backlash: A Real-World Case Study

When HeroUI launched version 3, they made a classic UX mistake that sparked immediate backlash on Reddit. The documentation page had background music that autoplayed — and on mobile, there was no way to mute or pause it.

Here’s what users had to say:

“Looks really good. How do I stop the music on that page though?” — strblr

“On mobile at least, it can’t be muted or paused or anything. Made me immediately close the page without looking at the rest.” — cookies_are_awesome

“It’s a classic UX mistake known since the early 2000s.” — strblr

“Absolutely annoying to listen to that showcase music when all I’m to do is look at some syntax examples.” — AkiStudios1

The pattern is clear: users closed the page immediately. These weren’t just annoyed users — they were potential customers who never got to see the actual product because of an anti-pattern that should have died 20 years ago.

Why Autoplay Audio Fails: The Four Pillars of Failure

After experiencing this firsthand and seeing the reactions, I dug into why autoplay audio is so universally hated. It comes down to four fundamental problems:

1. Loss of Control

Users expect to control their browsing experience. When a website plays audio without consent, it strips away that control. This violates one of Nielsen’s heuristics: User Control and Freedom.

Think about it from the user’s perspective:

  • They didn’t ask for audio
  • They can’t predict when it will start
  • They scramble to find controls (if they exist)
  • They feel violated, not welcomed

2. Context Mismatch

I was in a coffee shop when the HeroUI music started. Others might be:

  • In an office with coworkers nearby
  • In a library
  • On public transportation
  • In a meeting with their microphone on
  • Listening to their own music or podcast

The website has zero context about the user’s environment. Autoplaying audio assumes a context that rarely exists.

3. Accessibility Violations

This isn’t just annoying — it’s an accessibility failure. WCAG 2.1 Guideline 1.4.2 (Audio Control) states:

“If any audio on a Web page plays automatically for more than 3 seconds, either a mechanism is available to pause or stop the audio, or a mechanism is available to control audio volume independently from the overall system volume level.”

Autoplay audio creates barriers for:

  • Screen reader users: Audio conflicts with screen reader output
  • Neurodivergent users: Unexpected stimuli can cause cognitive overload
  • Hard of hearing users: Sudden audio can be startling or confusing

4. Trust and Brand Damage

First impressions matter. When a user’s first interaction with your site is “find the mute button,” you’ve already lost. It signals:

  • “We care more about our ‘creative vision’ than your experience”
  • “We don’t understand basic web standards”
  • “We don’t test for real-world scenarios”

The Reddit thread about HeroUI v3? That’s negative word-of-mouth marketing that sticks.

Browsers Agree: Autoplay is Bad

This isn’t just a UX opinion — browser vendors have actively worked to prevent autoplay audio:

┌─────────────────────────────────────────────────────────────┐
│ Browser Autoplay Policies │
├─────────────────────────────────────────────────────────────┤
│ │
│ Chrome (since v66, 2018) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ✓ Muted autoplay: ALLOWED │ │
│ │ ✗ Autoplay with sound: BLOCKED │ │
│ │ • Requires user gesture (click/tap) │ │
│ │ • Media Engagement Index (MEI) affects permissions │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Safari / Firefox │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ✓ Muted autoplay: ALLOWED (with restrictions) │ │
│ │ ✗ Autoplay with sound: BLOCKED │ │
│ │ • Requires user interaction │ │
│ │ • iOS Low Power Mode blocks all autoplay │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Key Insight: Browsers are UX guardians. │
│ If they block your feature, it's probably bad UX. │
│ │
└─────────────────────────────────────────────────────────────┘

Fighting browser policy is a losing strategy. If Chrome, Safari, and Firefox all agree that autoplay audio is problematic, that should tell you something.

The Business Case: Lost Conversions

Let’s talk numbers. When users close your page immediately:

MetricImpact
Bounce RateSkyrockets — users leave before engaging
Dwell TimePlummets — no time to read content
Conversion RateDrops — bounced users don’t convert
SEO RankingSuffers — search engines detect poor UX
Brand PerceptionDamages — negative first impressions persist

The HeroUI case study shows this in action: multiple potential users closed the page before looking at the documentation. They never got to evaluate the product because of a preventable UX failure.

Let me show you the wrong way (what HeroUI did) and the right way to handle audio.

The Wrong Way (Don’t Do This)

<!-- This is what caused the HeroUI backlash -->
<audio autoplay loop>
<source src="background-music.mp3" type="audio/mpeg">
</audio>

This code:

  • Plays immediately without consent
  • Loops forever
  • Provides NO controls
  • Works… until browsers block it
<div class="audio-player" role="region" aria-label="Background music player">
<button
id="playPauseBtn"
aria-label="Play background music"
aria-pressed="false"
>
<svg class="play-icon" aria-hidden="true">
<!-- play icon SVG -->
</svg>
<span>Play Music</span>
</button>
</div>
<audio id="bgMusic" preload="none">
<source src="background-music.mp3" type="audio/mpeg">
</audio>
const playPauseBtn = document.getElementById('playPauseBtn');
const bgMusic = document.getElementById('bgMusic');
let isPlaying = false;
playPauseBtn.addEventListener('click', async () => {
try {
if (isPlaying) {
bgMusic.pause();
playPauseBtn.setAttribute('aria-label', 'Pause background music');
playPauseBtn.setAttribute('aria-pressed', 'false');
} else {
await bgMusic.play();
playPauseBtn.setAttribute('aria-label', 'Play background music');
playPauseBtn.setAttribute('aria-pressed', 'true');
}
isPlaying = !isPlaying;
} catch (error) {
// Autoplay was prevented - handle gracefully
console.log('Playback blocked by browser:', error);
isPlaying = false;
}
});
// Respect page visibility - pause when user leaves
document.addEventListener('visibilitychange', () => {
if (document.hidden && isPlaying) {
bgMusic.pause();
isPlaying = false;
playPauseBtn.setAttribute('aria-pressed', 'false');
}
});

WCAG-Compliant Implementation

For full accessibility compliance, you need more than just play/pause:

<div role="region" aria-label="Audio content player" class="audio-container">
<audio id="audioPlayer" preload="metadata">
<source src="podcast-episode.mp3" type="audio/mpeg">
<track kind="captions" src="captions.vtt" label="Captions">
</audio>
<div class="audio-controls">
<!-- Play/Pause -->
<button id="playBtn" aria-label="Play" aria-pressed="false">
Play
</button>
<!-- Progress bar -->
<div class="progress-container">
<label for="progressBar" class="visually-hidden">
Playback progress
</label>
<input
type="range"
id="progressBar"
min="0"
max="100"
value="0"
aria-label="Seek audio"
>
<span id="currentTime">0:00</span>
<span id="duration">0:00</span>
</div>
<!-- Volume control -->
<div class="volume-control">
<label for="volumeSlider" class="visually-hidden">Volume</label>
<input
type="range"
id="volumeSlider"
min="0"
max="1"
step="0.1"
value="0.8"
aria-label="Volume control"
>
<button id="muteBtn" aria-label="Mute" aria-pressed="false">
Mute
</button>
</div>
</div>
<!-- Text alternative for accessibility -->
<details class="audio-transcript">
<summary>Read transcript</summary>
<div class="transcript-content">
Full transcript of audio content...
</div>
</details>
</div>

Key accessibility features:

  • Visible controls: All functions have UI elements
  • ARIA labels: Screen readers understand each control
  • Keyboard navigation: All controls are keyboard-accessible
  • Transcript: Deaf/hard-of-hearing users get text alternative
  • Visual indicators: Progress bar shows playback position

React/Next.js Component

Here’s a production-ready React component:

import { useState, useRef, useEffect, useCallback } from 'react';
interface AudioPlayerProps {
src: string;
title: string;
transcript?: string;
}
export function AudioPlayer({ src, title, transcript }: AudioPlayerProps) {
const audioRef = useRef<HTMLAudioElement>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [volume, setVolume] = useState(0.8);
const togglePlay = useCallback(async () => {
if (!audioRef.current) return;
try {
if (isPlaying) {
audioRef.current.pause();
} else {
await audioRef.current.play();
}
setIsPlaying(!isPlaying);
} catch (error) {
// Browser blocked autoplay - show user-friendly message
console.error('Playback blocked:', error);
setIsPlaying(false);
}
}, [isPlaying]);
const handleSeek = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!audioRef.current) return;
const time = Number(e.target.value);
audioRef.current.currentTime = time;
setCurrentTime(time);
};
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newVolume = Number(e.target.value);
if (audioRef.current) {
audioRef.current.volume = newVolume;
}
setVolume(newVolume);
};
// Update time display
useEffect(() => {
const audio = audioRef.current;
if (!audio) return;
const handleTimeUpdate = () => setCurrentTime(audio.currentTime);
const handleLoadedMetadata = () => setDuration(audio.duration);
const handleEnded = () => setIsPlaying(false);
audio.addEventListener('timeupdate', handleTimeUpdate);
audio.addEventListener('loadedmetadata', handleLoadedMetadata);
audio.addEventListener('ended', handleEnded);
return () => {
audio.removeEventListener('timeupdate', handleTimeUpdate);
audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
audio.removeEventListener('ended', handleEnded);
};
}, []);
// Pause when component unmounts (page navigation)
useEffect(() => {
return () => {
audioRef.current?.pause();
};
}, []);
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
return (
<div className="audio-player" role="region" aria-label={title}>
<audio ref={audioRef} src={src} preload="metadata" />
<div className="controls">
<button
onClick={togglePlay}
aria-pressed={isPlaying}
aria-label={isPlaying ? 'Pause' : 'Play'}
>
{isPlaying ? 'Pause' : 'Play'}
</button>
<input
type="range"
min="0"
max={duration || 0}
value={currentTime}
onChange={handleSeek}
aria-label="Seek"
/>
<span aria-live="off">
{formatTime(currentTime)} / {formatTime(duration)}
</span>
<input
type="range"
min="0"
max="1"
step="0.1"
value={volume}
onChange={handleVolumeChange}
aria-label="Volume"
/>
</div>
{transcript && (
<details>
<summary>Read transcript</summary>
<div>{transcript}</div>
</details>
)}
</div>
);
}

When Is Audio Actually Appropriate?

I’m not saying “never use audio.” There are legitimate use cases:

Use CaseWhy It’s OK
Media playersUsers expect and initiate playback
GamesAudio is part of the experience
Screen readersAccessibility feature, not content
Music/podcast sitesCore functionality, expected behavior
Portfolio with videoIf controls are prominent and accessible

Even then, follow these rules:

  1. Never autoplay with sound — Start muted at most
  2. Provide obvious controls — Large buttons, clear labels
  3. One-click dismissal — Users should be able to mute instantly
  4. Remember preferences — Use localStorage to save mute state
  5. Mobile-first — Controls must work on touch screens
  6. Visual indicator — Show when audio is playing

What HeroUI Should Have Done

For a documentation site, background music serves no purpose. But if they really wanted it, here’s the approach:

┌────────────────────────────────────────────────────────────────┐
│ HEROUI v3 HOME PAGE │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Hero Section │ │
│ │ │ │
│ │ "Beautiful UI Components" │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ 🎵 Experience with Sound [▶ Play] │ │ │
│ │ │ │ │ │
│ │ │ "Click to enable ambient music for the full │ │ │
│ │ │ showcase experience" │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ✓ Audio is OPT-IN, not forced │
│ ✓ Clear affordance for interaction │
│ ✓ Users who don't care ignore it │
│ ✓ Users who want it click once │
│ │
└────────────────────────────────────────────────────────────────┘

The key difference: consent. Users choose to engage with audio, rather than being assaulted by it.

Audit Your Site Today

If you’re reading this and wondering if your own site has autoplay issues, check:

  1. Search your codebase for autoplay attributes
  2. Test in multiple browsers — Chrome, Firefox, Safari, mobile
  3. Test in different contexts — quiet room, headphones, no audio
  4. Check accessibility — Use a screen reader, try keyboard-only navigation
  5. Monitor analytics — Look for high bounce rates on pages with audio

The fix is straightforward: remove autoplay, add visible controls, respect user consent.

  • WCAG 2.1 Guideline 1.4.2: The formal accessibility requirement for audio control
  • Chrome Autoplay Policy: Technical details on browser restrictions
  • Media Engagement Index (MEI): How Chrome decides which sites can autoplay
  • Nielsen’s Heuristics: User control and freedom as a design principle

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments