Why Voice‑Powered Accessibility Matters
When you think about accessibility, you might picture screen readers, high‑contrast themes, or keyboard‑only navigation. While those are essential, voice interaction can be a game‑changer for users with visual impairments, motor challenges, or reading difficulties. By letting a device talk and listen, you give people a more natural way to consume content, control apps, and stay independent.
In this post we’ll walk through building a simple voice‑powered accessibility tool that:
- Converts on‑screen text to natural‑sounding speech (Text‑to‑Speech, TTS).
- Allows users to trigger actions with voice commands (Speech‑to‑Text, STT).
- Supports custom voice cloning so the output can match a brand or a user’s preferred tone.
We’ll use Python for the backend, the Web Speech API for browser‑side STT, and ElevenLabs for high‑quality TTS and voice cloning. The ElevenLabs API is straightforward, affordable, and produces studio‑grade audio that feels human‑like.
Quick link: Get started with ElevenLabs here → https://try.elevenlabs.io/kr07zfuqn1bp
Architecture Overview
+-------------------+ +--------------------+
| Front‑end (JS) | <---> | Backend (Python) |
| - SpeechRecognition API | - /synthesize endpoint
| - fetch() to /synthesize | - ElevenLabs TTS client
+-------------------+ +--------------------+
-
Front‑end captures user voice commands with the native
SpeechRecognitionAPI (Chrome, Edge, Safari). - When a command like “read this article” is detected, the page sends the target text to our Flask server.
- The server calls ElevenLabs’ TTS endpoint, optionally using a cloned voice, and streams back an MP3.
- The browser plays the audio, giving the user a seamless reading experience.
Setting Up the Python Backend
First, create a virtual environment and install the required packages:
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install flask requests python-dotenv
Create a .env file with your ElevenLabs API key (you can grab one from the dashboard after signing up):
ELEVENLABS_API_KEY=your_api_key_here
Flask App (app.py)
import os
from flask import Flask, request, jsonify, send_file
import requests
from dotenv import load_dotenv
from io import BytesIO
load_dotenv()
API_KEY = os.getenv("ELEVENLABS_API_KEY")
ELEVENLABS_TTS_URL = "https://api.elevenlabs.io/v1/text-to-speech"
app = Flask(__name__)
def synthesize(text: str, voice_id: str = "EXAVITQu4vr4xnSDxMaL") -> BytesIO:
"""
Calls ElevenLabs TTS and returns an in‑memory MP3 file.
The default voice_id is a high‑quality generic voice.
"""
url = f"{ELEVENLABS_TTS_URL}/{voice_id}"
headers = {
"xi-api-key": API_KEY,
"Content-Type": "application/json"
}
payload = {
"text": text,
"model_id": "eleven_monolingual_v1",
"voice_settings": {
"stability": 0.75,
"similarity_boost": 0.85
}
}
resp = requests.post(url, json=payload, headers=headers, stream=True)
resp.raise_for_status()
audio = BytesIO(resp.content)
audio.seek(0)
return audio
@app.route("/synthesize", methods=["POST"])
def synthesize_route():
data = request.get_json()
text = data.get("text", "")
voice_id = data.get("voice_id") # optional custom voice
if not text:
return jsonify({"error": "No text provided"}), 400
audio_io = synthesize(text, voice_id or "EXAVITQu4vr4xnSDxMaL")
return send_file(
audio_io,
mimetype="audio/mpeg",
as_attachment=False,
download_name="speech.mp3"
)
if __name__ == "__main__":
app.run(debug=True)
Key points
- The
synthesizefunction hides the HTTP details. -
voice_idcan be any voice you’ve created in ElevenLabs, making voice cloning trivial. - The endpoint returns raw MP3 data, which the browser can play instantly.
Front‑End: Capturing Voice Commands & Playing Audio
Create a simple index.html file. The example below uses vanilla JavaScript to keep the focus on the core concepts.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Voice‑Powered Reader</title>
<style>
body { font-family: sans-serif; margin: 2rem; }
#status { margin-top: 1rem; color: #555; }
</style>
</head>
<body>
<h1>Voice‑Powered Accessibility Demo</h1>
<p id="article">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur
pellentesque, odio nec pulvinar iaculis, nulla lorem fermentum urna,
a tincidunt massa neque non mauris.
</p>
<button id="listenBtn">🎤 Speak Command</button>
<div id="status"></div>
<script>
const listenBtn = document.getElementById('listenBtn');
const statusDiv = document.getElementById('status');
const articleText = document.getElementById('article').innerText;
// Check for SpeechRecognition support
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
statusDiv.textContent = "Your browser doesn't support SpeechRecognition.";
} else {
const recognizer = new SpeechRecognition();
recognizer.lang = 'en-US';
recognizer.interimResults = false;
recognizer.onresult = async (event) => {
const transcript = event.results[0][0].transcript.trim().toLowerCase();
statusDiv.textContent = `Heard: "${transcript}"`;
if (transcript.includes('read')) {
// Send article text to backend for TTS
const response = await fetch('/synthesize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: articleText })
});
const blob = await response.blob();
const audioURL = URL.createObjectURL(blob);
const audio = new Audio(audioURL);
audio.play();
} else {
statusDiv.textContent += " – command not recognized.";
}
};
recognizer.onerror = (e) => {
statusDiv.textContent = `Error: ${e.error}`;
};
listenBtn.addEventListener('click', () => {
statusDiv.textContent = 'Listening...';
recognizer.start();
});
}
</script>
</body>
</html>
What’s happening?
- Clicking “🎤 Speak Command” starts the SpeechRecognition engine.
- When the user says something containing the word “read”, we POST the article’s text to
/synthesize. - The server returns an MP3 stream, which we play with the native
AudioAPI.
Feel free to expand the command parser (e.g., “pause”, “stop”, “read next paragraph”) – the same pattern applies.
Adding a Custom Voice with ElevenLabs Voice Cloning
ElevenLabs makes voice cloning as easy as uploading a few minutes of audio. Here’s a quick walkthrough:
- Record a voice sample – 30 seconds to 5 minutes of clear speech.
- Upload via the dashboard → Voice Lab → Create New Voice.
-
Copy the generated
voice_id(a UUID string).
Now you can pass that voice_id to the /synthesize endpoint:
// Example: using a cloned voice
await fetch('/synthesize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: articleText,
voice_id: 'your-cloned-voice-id-here'
})
});
The result is a personalized reading experience that matches a brand mascot, a favorite narrator, or even the user’s own voice. For developers who need consistent, high‑quality audio at scale, ElevenLabs’ pricing and latency (sub‑second synthesis) make it a practical choice.
Want to try cloning your own voice? Sign up through this link → https://try.elevenlabs.io/kr07zfuqn1bp
Going Further: Adding Speech‑to‑Text on the Server
If you’d like to support voice commands that are processed server‑side (e.g., “translate this paragraph”), you can integrate ElevenLabs’ STT API or any other service like Whisper. The flow is similar:
- Capture audio on the client (
MediaRecorder). - POST the audio blob to
/transcribe. - The server forwards it to the STT endpoint and returns the transcript.
Here’s a minimal Flask route using ElevenLabs’ transcription endpoint:
@app.route("/transcribe", methods=["POST"])
def transcribe_route():
audio_file = request.files["audio"]
url = "https://api.elevenlabs.io/v1/speech-to-text"
headers = {"xi-api-key": API_KEY}
files = {"audio_file": audio_file}
resp = requests.post(url, headers=headers, files=files)
resp.raise_for_status()
return jsonify(resp.json())
Combine this with the earlier command parser and you have a fully voice‑driven UI.
Performance Tips & Best Practices
| Area | Recommendation |
|---|---|
| Network | Enable gzip/compression on Flask (Flask-Compress) to shrink MP3 payloads. |
| Caching | Cache repeated syntheses of the same text (hash → MP3) to avoid unnecessary API calls. |
| Accessibility | Always provide a visual fallback (e.g., a “Read Aloud” button) for browsers that don’t support SpeechRecognition. |
| Voice Selection | Let users pick a voice from a dropdown populated via GET https://api.elevenlabs.io/v1/voices. |
| Error Handling | Gracefully handle rate‑limit responses (429) by queuing requests or falling back to a slower TTS engine. |
Wrap‑Up
Building a voice‑powered accessibility tool is now within reach thanks to modern web APIs and services like ElevenLabs. With just a few lines of Python and JavaScript, you can:
- Turn any text into natural, human‑like speech.
- Offer custom voice clones for branding or personal preference.
- Enable hands‑free navigation through simple voice commands.
The core pieces are lightweight, open‑source, and can be expanded to support multilingual TTS, real‑time translation, or integration with existing assistive technologies.
Ready to give your users a voice?
Grab your ElevenLabs API key and start experimenting today → https://try.elevenlabs.io/kr07zfuqn1bp
Happy coding, and let’s make the web audible for everyone!
Top comments (0)