Text-to-Speech Converter with HTML, CSS & JavaScript
20 DAYS 20 PROJECT CHALLENGE
Day #18
Project Overview
Text to Speech Converter is a beginner-friendly project built using HTML, CSS, and JavaScript. It allows a user to enter text into the text area and then press the speak button to hear the text spoken by the browser. The application uses the Web Speech API to convert written text into speech. Users can choose from the available voices and use the Speak or Stop buttons to control the speech. The project displays how new features of browsers can be integrated into web applications without using a server backend.
Beginners can use this project to explore how modern web applications work. It introduces concepts such as browser APIs, user input handling, DOM manipulation, and event listeners.
Key Features
- Text to Speech Conversion: Users can enter text into the application and use JavaScript and the browser’s speech synthesis capability to convert the written information into speech.
- Text Input Area: A text area is provided in the application that allows a user to type, edit, or paste the information that needs to be read out by the application.
- Speech Synthesis API: The application uses the browser’s built-in Web Speech API to convert written text into speech without requiring users to upload audio files.
- Voice Selection: Users can select from the voices available in their browser or operating system. Different voices can provide different languages, accents, and speech styles.
- Speak Button: The speak button is a button that, when clicked, initiates the speech synthesis process, allowing the text that has been pasted to be read out loud.
- Stop Speech Option: Users can stop the currently playing speech when they no longer want the application to continue reading the text.
- Empty Text Validation: It can detect if a user has pasted any text within the text area provided to them. In cases where the text area is empty, users are offered a message reminding them to paste or type the text for the application to read out loud.
- Dynamic Voice Loading: JavaScript loads the voices available in the browser and adds them to the drop-down menu dynamically and put the options in a drop-down menu in real-time.
- Simple and Clean Interface: This project makes use of a simple and clean interface that ensures that the text area, the voice options, and the speak and stop options are all easy to locate and easy to use.
- Responsive Design: The Text to Speech Converter adjusts to different screen sizes, allowing users to use the application on desktop, tablet, and mobile devices.
- Easy Customization: The interface, options, controls, colors, and buttons of the application can be customized and modified to meet specific needs and requirements.
What You'll Learn
- Understand how the Web Speech API works.
- Learn how to use
speechSynthesisin JavaScript. - Practice creating a
SpeechSynthesisUtteranceobject. - Learn how to convert text into browser-generated speech.
- Understand how to retrieve available voices.
- Practice dynamically adding voice options to a
<select>element. - Learn how JavaScript handles button click events.
- Understand how to read values from a text area.
- Practice validating user input before performing an action.
- Learn how to start and stop speech using JavaScript.
- Understand how browser APIs can add functionality to a web application.
HTML Code
HTML creates the basic structure of the Text to Speech Converter. First, the main application container holds the heading, text area, voice selection menu, and speech control buttons. The <textarea> element provides a space where users can type, edit, or paste the text they want the browser to read aloud. Next, the <select> element displays the voices available in the user’s browser, allowing them to choose a preferred voice. The control buttons allow users to start or stop the speech. These elements use IDs or classes so JavaScript can select them and add the required functionality.
Finally, the CSS file is linked inside the <head> section to control the application’s appearance. The script.js file is loaded near the bottom of the <body>, allowing JavaScript to access the page elements after they are available.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Day 18 — Text-to-Speech Converter</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="card" role="main" aria-labelledby="title">
<header>
<div class="logo">TTS</div>
<div>
<h1 id="title">Day 18: Text-to-Speech Converter</h1>
<p class="lead">Convert text to speech using the Web Speech API. Choose a voice, adjust rate/pitch, then Play.</p>
</div>
</header>
<section class="panel">
<label class="field">
<span class="label">Text to speak</span>
<textarea id="text" rows="6" placeholder="Type or paste text here...">Hello — this is a text to speech test.</textarea>
</label>
<div class="row controls">
<label class="small">
Voice
<select id="voiceSelect" aria-label="Voice selection"></select>
</label>
<label class="small">
Rate <span id="rateVal">1</span>
<input id="rate" type="range" min="0.5" max="2" step="0.1" value="1" />
</label>
<label class="small">
Pitch <span id="pitchVal">1</span>
<input id="pitch" type="range" min="0" max="2" step="0.1" value="1" />
</label>
<label class="small">
Volume <span id="volVal">1</span>
<input id="volume" type="range" min="0" max="1" step="0.05" value="1" />
</label>
</div>
<div class="actions">
<button id="playBtn" class="btn">Play</button>
<button id="pauseBtn" class="btn secondary" disabled>Pause</button>
<button id="resumeBtn" class="btn secondary" disabled>Resume</button>
<button id="stopBtn" class="btn secondary" disabled>Stop</button>
</div>
<div id="status" class="muted" role="status" aria-live="polite">Ready.</div>
<details style="margin-top:12px">
<summary>How it works (short)</summary>
<p class="small">The script populates available voices (async), creates a <code>SpeechSynthesisUtterance</code> for the text, sets voice, rate, pitch and volume, and uses <code>speechSynthesis.speak()</code>. Pause/resume/stop use the API methods. Some browsers require a user gesture to start audio.</p>
</details>
</section>
</main>
<script src="script.js"></script>
</body>
</html>
CSS Code
CSS controls the appearance and layout of the Text to Speech Converter. First, it styles the main container with appropriate width, spacing, padding, rounded corners, and visual effects to create a clean interface. Next, the text area receives styling that gives users enough space to enter longer content comfortably. The voice selection menu also receives clear styling so users can easily select one of the available voices.
In addition, the action buttons use distinct styles that make the Speak and Stop controls easy to identify. Hover and focus effects can provide visual feedback when users interact with these buttons. Finally, responsive CSS rules adjust the layout according to the available screen size, keeping the application comfortable to use on desktops, tablets, and mobile devices.
:root {
--bg: #071026;
--card: #0b1220;
--accent: #7c3aed;
--muted: #9aa4b2;
--white: #e6eef6;
font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: #002252;
color: var(--white);
display: flex;
align-items: center;
justify-content: center;
padding: 28px;
}
.card {
width: min(920px, 96%);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
border-radius: 12px;
padding: 18px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
border: 1px solid rgba(255, 255, 255, 0.03);
}
header {
display: flex;
gap: 12px;
align-items: center;
}
.logo {
width: 44px;
height: 44px;
border-radius: 10px;
background: linear-gradient(135deg, var(--accent), #22c1c3);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
}
h1 {
margin: 0;
font-size: 18px;
}
.lead {
margin: 4px 0 12px;
color: var(--muted);
font-size: 14px
}
.panel {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 12px;
}
.field {
display: flex;
flex-direction: column;
}
.label {
font-size: 13px;
color: var(--muted);
margin-bottom: 6px;
}
textarea {
min-height: 96px;
padding: 12px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.04);
background: transparent;
color: inherit;
outline: none;
resize: vertical;
}
.row.controls {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
}
.small {
font-size: 13px;
color: var(--muted);
display: flex;
flex-direction: column;
gap: 6px;
min-width: 160px;
}
input[type=range] {
width: 160px
}
.actions {
display: flex;
gap: 8px;
align-items: center
}
.btn {
padding: 10px 14px;
border-radius: 10px;
border: 0;
background: linear-gradient(90deg, var(--accent), #22c1c3);
color: white;
font-weight: 600;
cursor: pointer
}
.btn.secondary {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.06);
color: var(--muted)
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed
}
.muted {
color: var(--muted);
font-size: 13px
}
.small-note {
font-size: 12px;
color: var(--muted)
} Javascript Code
JavaScript provides the main functionality of the Text to Speech Converter. First, it selects the text area, voice selection menu, and control buttons from the HTML. It uses speechSynthesis.getVoices() to retrieve the voices available in the user’s browser and these voices are then added dynamically to the voice selection menu. Since some browsers load voices asynchronously, JavaScript can also listen for the voiceschanged event and update the voice list when new voices become available.
When the user clicks the Speak button, JavaScript reads the text from the text area and creates a SpeechSynthesisUtterance object, then the selected voice is assigned to this object before the browser begins speaking the text using speechSynthesis.speak(). Stop button uses speechSynthesis.cancel() to stop the current speech immediately. Input validation can also prevent the application from attempting to speak when the text area is empty.
// Day 18 — Text-to-Speech Converter using Web Speech API
const textEl = document.getElementById('text');
const voiceSelect = document.getElementById('voiceSelect');
const rateEl = document.getElementById('rate');
const pitchEl = document.getElementById('pitch');
const volumeEl = document.getElementById('volume');
const rateVal = document.getElementById('rateVal');
const pitchVal = document.getElementById('pitchVal');
const volVal = document.getElementById('volVal');
const playBtn = document.getElementById('playBtn');
const pauseBtn = document.getElementById('pauseBtn');
const resumeBtn = document.getElementById('resumeBtn');
const stopBtn = document.getElementById('stopBtn');
const statusEl = document.getElementById('status');
let synth = window.speechSynthesis || null;
let voices = [];
let utter = null; // current SpeechSynthesisUtterance
// Utils
function setStatus(msg, isError=false) {
statusEl.textContent = msg;
statusEl.style.color = isError ? '#ffbaba' : '';
}
// Check support
if (!synth) {
// No Web Speech API support
setStatus('Speech Synthesis API not supported in this browser.', true);
// disable controls
[playBtn, pauseBtn, resumeBtn, stopBtn].forEach(b => b.disabled = true);
voiceSelect.disabled = true;
} else {
// populate voices (async; some browsers load voices after some time)
function loadVoices() {
voices = synth.getVoices().sort((a,b) => a.name.localeCompare(b.name));
voiceSelect.innerHTML = '';
voices.forEach((v,i) => {
const opt = document.createElement('option');
opt.value = i;
opt.textContent = `${v.name} ${v.lang ? ' — ' + v.lang : ''}${v.default ? ' (default)' : ''}`;
voiceSelect.appendChild(opt);
});
// choose a default voice close to user's lang or the first
const userLang = navigator.language || navigator.userLanguage || '';
const preferredIndex = voices.findIndex(v => v.lang && v.lang.startsWith(userLang.split('-')[0]));
voiceSelect.value = preferredIndex >= 0 ? preferredIndex : 0;
setStatus('Voices loaded. Ready.');
}
loadVoices();
// Chrome/Edge may fire 'voiceschanged'
synth.onvoiceschanged = loadVoices;
}
// Sync UI labels for sliders
rateEl.addEventListener('input', () => rateVal.textContent = rateEl.value);
pitchEl.addEventListener('input', () => pitchVal.textContent = pitchEl.value);
volumeEl.addEventListener('input', () => volVal.textContent = volumeEl.value);
// Create a new utterance with current settings
function createUtterance() {
if (!synth) return null;
const text = textEl.value.trim();
if (!text) return null;
const u = new SpeechSynthesisUtterance(text);
const voiceIndex = parseInt(voiceSelect.value, 10);
if (!isNaN(voiceIndex) && voices[voiceIndex]) u.voice = voices[voiceIndex];
u.rate = Number(rateEl.value) || 1;
u.pitch = Number(pitchEl.value) || 1;
u.volume = Number(volumeEl.value);
// event handlers
u.onstart = () => {
setStatus('Speaking...');
playBtn.disabled = true;
pauseBtn.disabled = false;
stopBtn.disabled = false;
resumeBtn.disabled = true;
};
u.onend = () => {
setStatus('Finished speaking.');
playBtn.disabled = false;
pauseBtn.disabled = true;
resumeBtn.disabled = true;
stopBtn.disabled = true;
utter = null;
};
u.onerror = (e) => {
console.error('Speech error', e);
setStatus('Speech error occurred.', true);
playBtn.disabled = false;
pauseBtn.disabled = true;
resumeBtn.disabled = true;
stopBtn.disabled = true;
utter = null;
};
u.onpause = () => {
setStatus('Paused.');
pauseBtn.disabled = true;
resumeBtn.disabled = false;
};
u.onresume = () => {
setStatus('Resumed.');
pauseBtn.disabled = false;
resumeBtn.disabled = true;
};
return u;
}
// Play
playBtn.addEventListener('click', () => {
if (!synth) return;
// If already speaking, stop first
if (synth.speaking) {
// stop current to restart with new settings
synth.cancel();
utter = null;
}
const u = createUtterance();
if (!u) {
setStatus('Please enter some text to speak.', true);
return;
}
utter = u;
// Some browsers require user gesture; we already are in click handler.
synth.speak(utter);
});
// Pause
pauseBtn.addEventListener('click', () => {
if (!synth) return;
if (synth.speaking && !synth.paused) {
synth.pause();
// onpause event will update UI
}
});
// Resume
resumeBtn.addEventListener('click', () => {
if (!synth) return;
if (synth.paused) {
synth.resume();
// onresume event will update UI
}
});
// Stop / Cancel
stopBtn.addEventListener('click', () => {
if (!synth) return;
if (synth.speaking) {
synth.cancel();
// onend/onerror will update UI
setStatus('Stopped.');
playBtn.disabled = false;
pauseBtn.disabled = true;
resumeBtn.disabled = true;
stopBtn.disabled = true;
utter = null;
}
});
// Optional: update UI when user changes voice while speaking — restart speech
voiceSelect.addEventListener('change', () => {
if (!synth) return;
if (synth.speaking) {
// restart with new voice
synth.cancel();
const u = createUtterance();
if (u) {
utter = u;
synth.speak(utter);
}
}
});
// If user edits text while speaking, consider restarting (simple approach)
textEl.addEventListener('input', () => {
if (!synth) return;
if (synth.speaking) {
// small debounce to avoid too frequent restarts
if (window._ttsTimer) clearTimeout(window._ttsTimer);
window._ttsTimer = setTimeout(() => {
if (synth.speaking) {
synth.cancel();
const u = createUtterance();
if (u) synth.speak(u);
}
}, 500);
}
});
// Initialize controls state
(function initControls() {
playBtn.disabled = false;
pauseBtn.disabled = true;
resumeBtn.disabled = true;
stopBtn.disabled = true;
})();
Related Projects
Day 16 : Scroll Progress Bar
Shows a progress bar at the top as the user scrolls down the page.
Concepts: scroll event, math calculations.
Day 20 : Music Player App
A mini music player with play, pause, next, and progress bar.
Concepts: Audio API, event listeners, state management.
Day 11 : Drum Kit
Play drum sounds when clicking buttons or pressing keys.
Concepts: Keyboard events, Audio API.