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.

</span>
 lang="en">

   charset="UTF-8" />
   name="viewport" content="width=device-width, initial-scale=1.0" />
  Text to Speech Application


  Text to Speech Converter

   class="container">
     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.
    

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

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

     class="status" id="statusText">Ready
  





    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.


  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;
  }




    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.


  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;
    }
  });




    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!