> ## Documentation Index
> Fetch the complete documentation index at: https://docs.responsivevoice.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Browser Support

ResponsiveVoice targets modern evergreen browsers (released \~2020 onward), with automatic fallback. Base text-to-speech needs the Web Speech API or a `fetch`-capable runtime; some features require newer browsers — see [Feature requirements](#feature-requirements).

Support is shown per delivery method. The **browser bundle (CDN)** is Babel + core-js polyfilled, so it reaches the widest range; **`@responsivevoice/core`** and **`@responsivevoice/api-client`** are raw npm packages whose minimum versions reflect the browser APIs they use — your bundler supplies any transpilation.

| Package                     | Chrome | Firefox | Safari | Edge | iOS Safari | Chrome Android | Android WebView |
| --------------------------- | ------ | ------- | ------ | ---- | ---------- | -------------- | --------------- |
| Browser bundle (CDN)        | 69+    | 65+     | 12+    | 79+  | 12+        | 69+            | 69+             |
| @responsivevoice/core       | 69+    | 65+     | 14+    | 79+  | 14+        | 69+            | 69+             |
| @responsivevoice/api-client | 66+    | 65+     | 12+    | 79+  | 12+        | 66+            | 66+             |

*Minimum versions are generated from the browser APIs each package uses (MDN Browser Compat Data). npm packages are raw and unpolyfilled; the browser bundle adds Babel + core-js polyfills.*

> [!NOTE]
> These npm minimum versions are raw and unpolyfilled. To support older browsers, load the CDN bundle (already polyfilled) or add polyfills in your build — see [Polyfilling for older browsers](#polyfilling-for-older-browsers).

> [!TIP]
> `@responsivevoice/types` is pure TypeScript with no browser runtime requirements.

## Feature requirements

Some capabilities need newer browsers than the base minimum. All are degradable — when the API is missing, ResponsiveVoice falls back automatically.

| Feature                    | Requires          | Minimum                                            |
| -------------------------- | ----------------- | -------------------------------------------------- |
| HTTP/WebSocket streaming   | MediaSource       | iOS Safari 13+                                     |
| Web player (in-page UI)    | Shadow DOM        | Firefox 63+, Edge 79+                              |
| Native (Web Speech) voices | `speechSynthesis` | unavailable in Android WebView (falls back to API) |

> [!NOTE]
> Firefox has limited native voice support. ResponsiveVoice automatically uses the fallback API when needed.

## Polyfilling for older browsers

The npm packages ship modern JavaScript and leave transpilation to your bundler — the common convention. To support older browsers, add [core-js](https://github.com/zloirock/core-js) through [@babel/preset-env](https://babeljs.io/docs/babel-preset-env) with a browserslist target:

```js
// babel.config.js
module.exports = {
  presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: '3' }]],
};
```

This polyfills JavaScript built-ins like `URLSearchParams` and `Promise`. DOM and platform APIs (Shadow DOM, MediaSource) can't be polyfilled — those features need their listed browsers, but base text-to-speech still works without them.

## How Fallback Works

```d2 sketch
"User calls speak(...)" -> Native \n available?
Native \n available?.shape: diamond
Native \n available? -> Use Web Speech API: Yes
Native \n available? -> Use ResponsiveVoice API: No
Use ResponsiveVoice API -> Fetch audio from server
Fetch audio from server.shape: cloud
Fetch audio from server -> Play via Audio element
```

## Native vs Fallback

| Feature         | Native (Web Speech API) | Fallback (API) |
| --------------- | ----------------------- | -------------- |
| Latency         | Instant                 | \~100–300 ms\* |
| Offline         | Yes                     | No             |
| Voice quality   | Varies by OS            | Consistent     |
| Boundary events | Yes                     | No             |
| SSML support    | No                      | No             |

> [!NOTE]
> \* Fallback synthesis typically returns audio in \~100–300 ms; cached phrases in under 100 ms. Longer or uncached requests may take up to \~700 ms. These figures are indicative, not a guarantee.

## Platform-Specific Notes

### Chrome / Chromium

- Best voice selection with Google TTS voices
- Voices like "Google UK English Female" available
- Full boundary event support
- May require user interaction to start audio

### Safari / iOS

- Apple voices with "(Enhanced)" variants
- Good offline voice quality
- iOS requires user gesture to begin playback
- Some voices locked to specific iOS versions

### Firefox

- Limited native voice support
- Falls back to API more frequently
- No boundary events when using fallback
- Full functionality with API fallback

### Edge

- Windows voices available (David, Zira, etc.)
- Good Web Speech API support
- Microsoft neural voices on Windows 10+

## Mobile Considerations

### iOS

```typescript
// Optional: trigger speech from your own button instead of the built-in prompt
document.getElementById('speakBtn').addEventListener('click', () => {
  rv.speak('Hello from iOS', 'UK English Female');
});
```

> [!TIP]
> Mobile browsers (iOS, and Android Chrome) block autoplay, so audio needs a user gesture — but ResponsiveVoice handles this automatically with a built-in permission prompt that captures the first tap and unlocks audio. You can disable it and drive speech from your own UI (like the button above). See [the FAQ](/guides/faq/#does-responsivevoice-work-on-ios).

### Android

- Android Chrome requires a user gesture before audio — handled automatically by the same built-in permission prompt as iOS
- Voice availability varies by device manufacturer
- Google TTS voices commonly available
- May need to download voices in system settings

### Android WebView

> [!WARNING]
> The Web Speech API (native TTS) is **not supported** in Android WebView.
> ResponsiveVoice automatically falls back to the API for all speech synthesis.

- Minimum supported: Android WebView 66+
- Native TTS unavailable - always uses API fallback
- Audio playback works normally via Audio element
- Common in hybrid apps (Cordova, Capacitor, React Native WebView)

## Node.js Support

ResponsiveVoice works in Node.js environments using the [`@responsivevoice/api-client`](/getting-started/installation/#api-client) package. The minimum Node.js version varies by package:

| Package                          | Minimum Node.js |
| -------------------------------- | --------------- |
| `@responsivevoice/types`, `text` | 14+             |
| `@responsivevoice/api-client`    | 16+             |

`@responsivevoice/core` and `@responsivevoice/features` target the browser — Node is only their build environment (bundle with Vite/webpack), not a runtime.

### Node.js 18+ (recommended)

All features work out of the box with native `fetch`, `Blob`, and `AbortController`:

```typescript
import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client';

const client = new ResponsiveVoiceAPIClient({
  apiKey: process.env.RESPONSIVEVOICE_API_KEY,
  apiSecret: process.env.RESPONSIVEVOICE_API_SECRET,
});

const audio = await client.synthesize({
  text: 'Hello from Node.js',
  voice: 'UK English Female',
});
```

### Node.js 16–17

Native `fetch` is not available. Pass a fetch implementation via the `fetch` config option:

```typescript
import fetch from 'node-fetch';
import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client';

const client = new ResponsiveVoiceAPIClient({
  apiKey: process.env.RESPONSIVEVOICE_API_KEY,
  apiSecret: process.env.RESPONSIVEVOICE_API_SECRET,
  fetch,
});
```

### WebSocket streaming on Node.js \< 22

The global `WebSocket` was added in Node.js 22. On older versions, pass a WebSocket implementation:

```typescript
import WebSocket from 'ws';
import { WebSocketConnection } from '@responsivevoice/api-client';

const ws = new WebSocketConnection({
  baseUrl: 'https://texttospeech.responsivevoice.org',
  apiKey: process.env.RESPONSIVEVOICE_API_KEY,
  WebSocket,
});
```

## Feature Detection

Check platform capabilities:

```typescript
// Check if the Web Speech API is available
const hasNativeTTS = 'speechSynthesis' in window;

// Whether ResponsiveVoice can use native voices on this platform
const nativeSupported = rv.isNativeSupported();

// Check if a voice name is in the resolvable catalog
const voices = rv.getVoices();
const hasUKFemale = voices.some((v) => v.name === 'UK English Female');

// Native <-> fallback switches are reported via the OnServiceSwitched event
rv.addEventListener('OnServiceSwitched', (payload) => {
  console.log(`Switched from ${payload.from} to ${payload.to}`);
});
```

## Forcing Fallback

Force server (fallback) audio even when native voices exist — set it at init or toggle at runtime:

```typescript
// At init
const rv = await getResponsiveVoice({
  apiKey: 'YOUR_API_KEY',
  forceFallback: true,
});

// Or at runtime
rv.setForceFallback(true);
```

## User Interaction Requirements

Modern browsers require user interaction before playing audio:

```typescript
// ❌ This may be blocked
window.onload = () => {
  rv.speak('Hello'); // Blocked by autoplay policy
};

// ✅ This works
button.onclick = () => {
  rv.speak('Hello'); // Allowed - user initiated
};
```

### Workaround: Initialize on First Interaction

On mobile, ResponsiveVoice's built-in permission prompt already unlocks audio on the first gesture — use this manual pattern only if you've disabled the prompt and want to unlock from your own handler:

```typescript
let initialized = false;

document.addEventListener(
  'click',
  () => {
    if (!initialized) {
      rv.speak('', 'UK English Female'); // Silent init
      initialized = true;
    }
  },
  { once: true },
);
```

## Testing Across Platforms

```typescript
// Log platform info for debugging
console.log({
  browser: navigator.userAgent,
  hasNativeTTS: 'speechSynthesis' in window,
  nativeVoices: window.speechSynthesis?.getVoices().length ?? 0,
  rvVoices: rv.getVoices().length,
});
```
