DEV Community

Cover image for Making Your Website Talk (Without Scaring Your Users)
Jerry Satpathy
Jerry Satpathy

Posted on

Making Your Website Talk (Without Scaring Your Users)

Let’s be honest: most websites are too quiet. We spend our lives staring at glowing rectangles, reading text like it’s 1995. But your browser has a hidden superpower called the Web Speech API. It can talk. And no, I don't mean those weird auto-playing video ads from 2008.

Building a TTS Weather App

Today, we’re building a Weather Dashboard that doesn’t just show you the temperature—it summarises the vibes and reads them to you.

The Stack

  • React.js: Because we like components and state management that makes sense.

  • Web Speech API: Specifically window.speechSynthesis.

  • OpenWeatherMap API: For the data (or any weather API of your choice).


1. The "Brains": Fetching & Summarising

First, we need something worth saying. A raw JSON response saying {"temp": 273.15} is great for machines, but humans prefer "It's freezing, stay inside."

const fetchWeatherSummary = async (city) => {
  const response = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY&units=metric`);
  const data = await response.json();

  // A simple summarizer (You could pipe this to an LLM for more "flavour")
  return `Currently in ${data.name}, it's ${data.main.temp} degrees with ${data.weather[0].description}. My professional advice? ${data.main.temp > 25? "Wear a hat.":"Grab a jacket."}`;
};

Enter fullscreen mode Exit fullscreen mode

2. The "Voice": Meet SpeechSynthesis

The window.speechSynthesis controller is the boss. It manages the queue of "utterances" (the things you want to say).

The useSpeech Hook

Don't clutter your UI logic. Let’s wrap the speech engine in a clean React hook.

import { useState, useEffect } from 'react';

export const useSpeech = () => {
  const [voices, setVoices] = useState<SpeechSynthesisVoice[]>([]);

  useEffect(() => {
    const updateVoices = () => {
      setVoices(window.speechSynthesis.getVoices());
    };

    window.speechSynthesis.onvoiceschanged = updateVoices;
    updateVoices();
  }, []);

  const speak = (text: string, voiceName?: string, rate = 1, pitch = 1) => {
    // Cancel any ongoing chatter
    window.speechSynthesis.cancel();

    const utterance = new SpeechSynthesisUtterance(text);

    // Find the voice or default to the first one
    if (voiceName) {
      utterance.voice = voices.find(v => v.name === voiceName) || null;
    }

    utterance.rate = rate;   // 0.1 to 10
    utterance.pitch = pitch; // 0 to 2

    window.speechSynthesis.speak(utterance);
  };

  return { speak, voices, pause: () => window.speechSynthesis.pause(), resume: () => window.speechSynthesis.resume() };
};

Enter fullscreen mode Exit fullscreen mode

3. The Dashboard Component

Now, let’s glue it together. We’ll create a button that fetches the data and then reads it out loud.

import React, { useState } from 'react';
import { useSpeech } from './hooks/useSpeech';

const WeatherDashboard = () => {
  const [summary, setSummary] = useState("");
  const { speak, voices } = useSpeech();

  const handleWeatherUpdate = async () => {
    const text = await fetchWeatherSummary("London");
    setSummary(text);
    speak(text, voices[0]?.name, 1.1, 1);
  };

  return (
    <div className="p-8 max-w-md mx-auto bg-white rounded-xl shadow-md">
      <h1 className="text-2xl font-bold">Weather Talker</h1>
      <button 
        onClick={handleWeatherUpdate}
        className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        Get Audio Summary
      </button>
      {summary && <p className="mt-4 italic text-gray-600">"{summary}"</p>}
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

Pro-Tips for the Speech API

  1. Voices are Lazy: Voices don't always load immediately. That onvoiceschanged event in our hook is crucial; otherwise, your voices array will be empty on the first render.

  2. User Activation: Browsers hate noise. You cannot trigger speak() on page load. It must be inside a user-triggered event (like a click).

  3. The "Vocalize" Shortcut: If you want to skip the boilerplate, there is a handy NPM package called vocalize.ts. It’s a TypeScript-first wrapper for this exact API. Full disclosure: it’s currently in a bit of a dormant state, but the core logic is solid, and an update is coming soon to modernise the internals.

Why bother?

Accessibility is the obvious answer. Screen readers are great, but sometimes a specific "Read this for me" feature is exactly what a user needs when they're multitasking or vision-impaired.

Plus, it's just fun to make your computer talk back to you. Just don't give it a sarcastic personality unless you're prepared for the consequences.

And as always Happy coding!

Top comments (0)