In this article, we’ll build a simple web tool to explore how Text-to-Speech (TTS) works in JavaScript. We’ll also dive into the logic of sentence-level highlighting. These two features are often combined to create accessible, dynamic reading experiences in the browser.
We’ll go step-by-step:
Learn how TTS works in the browser Explore how to highlight sentences dynamically Build a working mini tool with HTML, CSS, and JavaScript (Demo & Code)
📢 What is TTS in the browser?
JavaScript provides built-in support for speech synthesis using the SpeechSynthesis API. It allows us to speak text out loud using voices available in the system.
Core objects:
speechSynthesis — controller to play, pause, stop, and resume speech
— controller to play, pause, stop, and resume speech SpeechSynthesisUtterance — represents the text that will be spoken
✨ Simple example:
js Copy Copied! 1 2 const msg = new SpeechSynthesisUtterance("Hello, world!"); window.speechSynthesis.speak(msg);
⚙️ Add voice and settings:
js Copy Copied! 1 2 3 4 5 6 const utter = new SpeechSynthesisUtterance("This is a test"); const voices = window.speechSynthesis.getVoices(); utter.voice = voices.find(v => v.lang === 'en-US'); utter.rate = 1.2; utter.pitch = 1; window.speechSynthesis.speak(utter);
You can also track when speech starts and ends:
js Copy Copied! 1 2 utter.onstart = () => console.log('Started speaking'); utter.onend = () => console.log('Finished speaking');
✍️ Sentence-level Highlighting
To show which sentence is currently being read, we’ll highlight that sentence using CSS and JavaScript.
Example HTML:
html Copy Copied! 1 2 3 4 Interactive TTS Article Reader
First sentence. Second sentence.
CSS for highlighting: css Copy Copied! 1 2 3 4 .sentence.active { background-color: yellow; font-weight: bold; } JavaScript highlighting logic: js Copy Copied! 1 2 3 4 5 function highlight(index) { document.querySelectorAll('.sentence').forEach((el, i) => { el.classList.toggle('active', i === index); }); } 🚀 Project: Reader with TTS and Highlighting Our tool will: Read sentences one-by-one Highlight the current sentence Provide Play, Pause, Resume, Stop buttons Let the user select a voice 📄 HTML Structure html Copy Copied! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31Read Along TTS Demo
Learning to code is a never-ending journey. Technologies evolve rapidly, requiring constant adaptation. JavaScript, HTML, and CSS are essential tools for web development. Frameworks like React and Vue enhance front-end capabilities. Back-end skills with Node.js extend JavaScript to the server.
0/0
🎨 CSS Styling
css Copy Copied! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 body { font-family: sans-serif; margin: 0; padding: 2rem; background: #f0f4f8; color: #333; } h1 { text-align: center; margin-bottom: 2rem; } .toolbar { display: flex; justify-content: center; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem; } .toolbar button, .toolbar select { padding: 0.6rem 1.2rem; border-radius: 4px; border: none; font-size: 1rem; } .toolbar button { background: #0077cc; color: white; cursor: pointer; } .toolbar button:disabled { background: #ccc; cursor: not-allowed; } .text-block { background: white; padding: 1.5rem; border-radius: 6px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); } .line { display: block; margin-bottom: 1rem; cursor: pointer; transition: background 0.3s; } .line:hover { background: #f9f9f9; } .line.active { background: #fff3cd; font-weight: bold; } .progress { text-align: center; margin-top: 1rem; } .bar { height: 8px; width: 100%; background: #eee; border-radius: 4px; overflow: hidden; } .bar-fill { height: 100%; width: 0; background: linear-gradient(to right, #0077cc, #005fa3); transition: width 0.3s; }
💡 JavaScript Logic
js Copy Copied! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 const lines = document.querySelectorAll('.line'); const playBtn = document.getElementById('start'); const pauseBtn = document.getElementById('pause'); const resumeBtn = document.getElementById('resume'); const stopBtn = document.getElementById('stop'); const resetBtn = document.getElementById('reset'); const voiceSelect = document.getElementById('voices'); const progressText = document.getElementById('progressText'); const progressBar = document.getElementById('bar'); const synth = window.speechSynthesis; let voices = []; let currentIndex = 0; let currentUtterance = null; let isPaused = false; function populateVoices() { voices = synth.getVoices(); voiceSelect.innerHTML = ''; voices.forEach((voice, index) => { const opt = document.createElement('option'); opt.value = index; opt.textContent = `${voice.name} (${voice.lang})`; voiceSelect.appendChild(opt); }); } synth.onvoiceschanged = populateVoices; populateVoices(); function updateUI() { progressText.textContent = `${currentIndex + 1}/${lines.length}`; progressBar.style.width = `${((currentIndex + 1) / lines.length) * 100}%`; } function highlightLine(index) { lines.forEach((line, i) => { line.classList.toggle('active', i === index); }); lines[index]?.scrollIntoView({ behavior: 'smooth', block: 'center' }); } function speakLine(index) { if (index >= lines.length) return; const text = lines[index].textContent; const utter = new SpeechSynthesisUtterance(text); const selected = voiceSelect.value; if (voices[selected]) utter.voice = voices[selected]; utter.rate = 1; utter.onstart = () => { highlightLine(index); playBtn.disabled = true; pauseBtn.disabled = false; resumeBtn.disabled = true; stopBtn.disabled = false; }; utter.onend = () => { if (!isPaused) { currentIndex++; if (currentIndex < lines.length) { speakLine(currentIndex); } else { resetControls(); } } }; currentUtterance = utter; synth.speak(utter); updateUI(); } function resetControls() { playBtn.disabled = false; pauseBtn.disabled = true; resumeBtn.disabled = true; stopBtn.disabled = true; } playBtn.onclick = () => { currentIndex = 0; speakLine(currentIndex); }; pauseBtn.onclick = () => { synth.pause(); isPaused = true; pauseBtn.disabled = true; resumeBtn.disabled = false; }; resumeBtn.onclick = () => { synth.resume(); isPaused = false; pauseBtn.disabled = false; resumeBtn.disabled = true; }; stopBtn.onclick = () => { synth.cancel(); resetControls(); }; resetBtn.onclick = () => { synth.cancel(); currentIndex = 0; highlightLine(currentIndex); updateUI(); }; lines.forEach((line, index) => { line.addEventListener('click', () => { synth.cancel(); currentIndex = index; speakLine(currentIndex); }); }); updateUI();
✅ How it All Works Together
Each sentence is a
We iterate over them and use SpeechSynthesisUtterance to read aloud
to read aloud While speaking, we highlight the current sentence and scroll to it
On speech end, the next sentence is read automatically
🔚 Conclusion
Now you understand:
How browser TTS works
How to apply dynamic sentence highlighting
How to build a full-featured reading UI from scratch
You can extend this project by: