DEV Community

CodeWithDhanian
CodeWithDhanian

Posted on

🗣️ Build a Full Text-to-Speech Web App with HTML, CSS & JavaScript

Looking for a practical, beginner-friendly JavaScript project that’s both fun and useful? Let’s build a fully functional Text-to-Speech App using nothing but HTML, CSS, and Vanilla JavaScript! This app lets users input any text, choose different voices, and control pitch and rate before converting the text to spoken audio using the Web Speech API.

💡 Live Demo & Source Code:

👉 Check it out on CodePen


📦 What We'll Cover

  • ✅ Clean and functional HTML layout
  • 🎨 Responsive and styled interface with CSS
  • ⚙️ Full JavaScript integration using the Web Speech API
  • 🔄 Voice selection, pitch and rate controls
  • ⏯️ Speak, Pause/Resume, and Stop functionalities

1️⃣ HTML: The Structure of the App

We'll begin with the base layout. It includes a text area, dropdown for voices, sliders for rate and pitch, and three buttons for controls.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Text to Speech Application</title>
</head>
<body>
  <h1>Text to Speech Converter</h1>

  <div class="container">
    <textarea id="textInput" placeholder="Enter text to convert to speech...">
Hello! This is a text to speech demo. You can change my voice, rate, and pitch using the controls below.
    </textarea>

    <div class="controls">
      <div>
        <label for="voiceSelect">Voice:</label>
        <select id="voiceSelect"></select>
      </div>
      <div>
        <label for="rate">Rate: <span id="rateValue">1</span></label>
        <input type="range" id="rate" min="0.5" max="2" value="1" step="0.1" />
      </div>
      <div>
        <label for="pitch">Pitch: <span id="pitchValue">1</span></label>
        <input type="range" id="pitch" min="0.5" max="2" value="1" step="0.1" />
      </div>
    </div>

    <div class="buttons">
      <button id="speakBtn">Speak</button>
      <button id="pauseBtn">Pause/Resume</button>
      <button id="stopBtn">Stop</button>
    </div>

    <p class="status" id="statusText">Ready</p>
  </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

2️⃣ CSS: Making It Beautiful & Responsive

Next, we’ll style the app to give it a polished, modern look.

<style>
  body {
    font-family: Arial, sans-serif;
    max-width: 1000px;
    margin: 0 auto;
    padding: 30px;
    line-height: 1.6;
    background-color: #f4f4f4;
  }

  h1 {
    text-align: center;
    color: #333;
    margin-bottom: 30px;
  }

  .container {
    background-color: #fff;
    border-radius: 12px;
    padding: 30px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
  }

  textarea {
    width: 100%;
    height: 180px;
    padding: 15px;
    margin-bottom: 25px;
    border-radius: 8px;
    border: 1px solid #ccc;
    resize: vertical;
    font-size: 16px;
  }

  .controls {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 25px;
  }

  .controls > div {
    flex: 1;
    min-width: 240px;
  }

  label {
    display: block;
    font-weight: bold;
    margin-bottom: 8px;
  }

  select, input[type=range] {
    width: 100%;
    padding: 10px;
    font-size: 16px;
    border-radius: 8px;
    border: 1px solid #ddd;
  }

  .buttons {
    display: flex;
    gap: 15px;
  }

  button {
    flex: 1;
    padding: 14px;
    font-size: 16px;
    background-color: #3498db;
    color: white;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: 0.3s ease;
  }

  button:hover {
    background-color: #2980b9;
  }

  #pauseBtn {
    background-color: #f39c12;
  }

  #pauseBtn:hover {
    background-color: #e67e22;
  }

  #stopBtn {
    background-color: #e74c3c;
  }

  #stopBtn:hover {
    background-color: #c0392b;
  }

  .status {
    text-align: center;
    font-style: italic;
    margin-top: 20px;
    color: #666;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

3️⃣ JavaScript: Adding the Speech Magic

Let’s now handle all the logic—selecting voices, adjusting pitch and rate, and playing the speech.

<script>
  const synth = window.speechSynthesis;
  let voices = [];
  let currentUtterance = null;
  let isPaused = false;

  const textInput = document.getElementById('textInput');
  const voiceSelect = document.getElementById('voiceSelect');
  const rate = document.getElementById('rate');
  const rateValue = document.getElementById('rateValue');
  const pitch = document.getElementById('pitch');
  const pitchValue = document.getElementById('pitchValue');
  const speakBtn = document.getElementById('speakBtn');
  const pauseBtn = document.getElementById('pauseBtn');
  const stopBtn = document.getElementById('stopBtn');
  const statusText = document.getElementById('statusText');

  function populateVoiceList() {
    voices = synth.getVoices();
    voiceSelect.innerHTML = '';
    voices.forEach((voice, index) => {
      const option = document.createElement('option');
      option.textContent = `${voice.name} (${voice.lang})`;
      option.value = index;
      voiceSelect.appendChild(option);
    });
  }

  if (synth.onvoiceschanged !== undefined) {
    synth.onvoiceschanged = populateVoiceList;
  } else {
    populateVoiceList();
  }

  rate.addEventListener('input', () => {
    rateValue.textContent = rate.value;
  });

  pitch.addEventListener('input', () => {
    pitchValue.textContent = pitch.value;
  });

  function speak() {
    if (synth.speaking) {
      synth.cancel();
    }

    const text = textInput.value.trim();
    if (!text) {
      statusText.textContent = 'Please enter some text.';
      return;
    }

    currentUtterance = new SpeechSynthesisUtterance(text);
    currentUtterance.voice = voices[voiceSelect.value];
    currentUtterance.rate = parseFloat(rate.value);
    currentUtterance.pitch = parseFloat(pitch.value);

    currentUtterance.onstart = () => {
      statusText.textContent = 'Speaking...';
      speakBtn.disabled = true;
    };

    currentUtterance.onend = () => {
      statusText.textContent = 'Finished speaking.';
      speakBtn.disabled = false;
      isPaused = false;
    };

    currentUtterance.onerror = (event) => {
      statusText.textContent = `Error: ${event.error}`;
      speakBtn.disabled = false;
    };

    synth.speak(currentUtterance);
  }

  speakBtn.addEventListener('click', speak);

  pauseBtn.addEventListener('click', () => {
    if (!synth.speaking) return;

    if (isPaused) {
      synth.resume();
      isPaused = false;
      statusText.textContent = 'Resumed speaking.';
    } else {
      synth.pause();
      isPaused = true;
      statusText.textContent = 'Paused.';
    }
  });

  stopBtn.addEventListener('click', () => {
    if (synth.speaking) {
      synth.cancel();
      isPaused = false;
      statusText.textContent = 'Speech stopped.';
      speakBtn.disabled = false;
    }
  });
</script>
Enter fullscreen mode Exit fullscreen mode

🔍 Demo and Source Code

🎯 Want to see the app live and interact with it right now?

Live Demo + Full Source Code:

👉 https://codepen.io/Code-WithDhanian/pen/RNNNMVd


🎁 Learn More, Build More!

If you love practical JavaScript projects like this one and want more ready-to-use examples, check out my digital products, courses, and ebooks on Gumroad!

📘 Support & Buy My JavaScript Ebooks and Projects:

👉 https://codewithdhanian.gumroad.com


🙌 Final Thoughts

You’ve just built a real-world, browser-powered Text-to-Speech App with full voice, pitch, and rate control—all in pure HTML, CSS, and JavaScript. This project is an awesome addition to your portfolio and a perfect introduction to Web APIs.

Thanks for building with me! Feel free to share, remix, and improve this app for your own creative needs!

Top comments (2)

Collapse
 
georgeokinda profile image
George Sino

Very helpful! thanks Dhanian. God bless the work of your hands in Christ Jesus. Just noticed some complex text like make the system through sythensis-failed error on android chrome mobile app and undefined error on Mozilla Mobile app but works well on Desktop for the same link. Here is an example of a failing one sinosoft.guru/knowledgebase/258/Be... if you might find a solution will be grateful and a working one sinosoft.guru/knowledgebase/193/Co...

Collapse
 
george221 profile image
Alexander George

Liked how you illustrated the procedure on how to make the text to speech web app