DEV Community

VoiceDeveloper
VoiceDeveloper

Posted on

ElevenLabs + React: Building a Voice-Enabled UI

Introduction

If you’ve ever wanted to give your web app a voice—whether it’s reading out notifications, narrating articles, or creating a fully‑spoken chatbot—you’re in the right place. In this walkthrough we’ll combine React with the powerful text‑to‑speech (TTS) and voice‑cloning capabilities of ElevenLabs to build a simple but extensible voice‑enabled UI.

By the end of the article you’ll have:

  • A React app that can send text to ElevenLabs and play back the generated audio.
  • A reusable useElevenLabs hook that abstracts the API calls.
  • An example of how to use a custom voice clone for a more personal feel.

Let’s dive in!

Why ElevenLabs?

ElevenLabs has quickly become a go‑to service for developers who need natural‑sounding speech. Their models support multiple languages, fine‑grained control over voice style, and most importantly voice cloning—you can upload a short sample of a speaker’s voice and generate speech that sounds just like them.

The API is straightforward (REST + JSON) and works nicely with browser‑based fetch calls, which means you can keep everything client‑side if you’re okay with exposing your API key in a development build (or you can proxy through your own backend for production).

You can sign up and get a free tier at the official link: https://try.elevenlabs.io/kr07zfuqn1bp.

Getting Started

First, make sure you have Node.js (v16+) installed. Then spin up a fresh React project with Vite—its zero‑config setup is perfect for demos:

npm create vite@latest elevenlabs-react-demo --template react
cd elevenlabs-react-demo
npm install
Enter fullscreen mode Exit fullscreen mode

Add a couple of tiny dependencies we’ll need for audio playback:

npm install --save-dev eslint-plugin-react
npm install howler
Enter fullscreen mode Exit fullscreen mode

Create a .env file in the project root and store your ElevenLabs API key (never commit this file to Git):

VITE_ELEVENLABS_API_KEY=your_api_key_here
Enter fullscreen mode Exit fullscreen mode

Vite automatically exposes variables prefixed with VITE_ to the client code.

Setting Up the ElevenLabs Service

Let’s wrap the raw fetch calls in a tiny utility module so we can reuse it across components.

src/services/elevenlabs.js

// src/services/elevenlabs.js
const API_KEY = import.meta.env.VITE_ELEVENLABS_API_KEY;
const BASE_URL = "https://api.elevenlabs.io/v1";

export async function generateSpeech({
  text,
  voiceId = "EXAVITQu4vr4xnSDxMaL", // default "Rachel" voice
  stability = 0.75,
  similarityBoost = 0.75,
}) {
  const response = await fetch(`${BASE_URL}/text-to-speech/${voiceId}`, {
    method: "POST",
    headers: {
      "xi-api-key": API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      text,
      voice_settings: {
        stability,
        similarity_boost: similarityBoost,
      },
    }),
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(err.error?.message ?? "ElevenLabs request failed");
  }

  // The API returns raw audio bytes (MP3) when you set `Accept: audio/mpeg`
  // but the default response is JSON with a temporary URL. We'll request the URL.
  const { audio_url } = await response.json();
  return audio_url;
}
Enter fullscreen mode Exit fullscreen mode

If you want to clone a voice, first upload a sample (see ElevenLabs docs) and retrieve the new voiceId. Then pass that ID to generateSpeech.

Building a Reusable Hook

React hooks make it easy to manage async state. Here’s a simple hook that calls our service and returns the audio URL.

src/hooks/useElevenLabs.js

import { useState } from "react";
import { generateSpeech } from "../services/elevenlabs";

export function useElevenLabs() {
  const [loading, setLoading] = useState(false);
  const [audioUrl, setAudioUrl] = useState(null);
  const [error, setError] = useState(null);

  const speak = async ({ text, voiceId }) => {
    setLoading(true);
    setError(null);
    try {
      const url = await generateSpeech({ text, voiceId });
      setAudioUrl(url);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  };

  return { speak, loading, audioUrl, error };
}
Enter fullscreen mode Exit fullscreen mode

Creating the VoiceButton Component

Now we can build a UI component that lets users type a sentence and hear it spoken.

src/components/VoiceButton.jsx

import { useState, useEffect } from "react";
import { useElevenLabs } from "../hooks/useElevenLabs";
import { Howl } from "howler";

export default function VoiceButton({ voiceId }) {
  const [text, setText] = useState("");
  const { speak, loading, audioUrl, error } = useElevenLabs();

  // Play the audio whenever a new URL appears
  useEffect(() => {
    if (!audioUrl) return;
    const sound = new Howl({
      src: [audioUrl],
      format: ["mp3"],
      html5: true,
    });
    sound.play();
    // Clean up when component unmounts or new audio loads
    return () => sound.unload();
  }, [audioUrl]);

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!text.trim()) return;
    speak({ text, voiceId });
  };

  return (
    <div style={{ maxWidth: "400px", margin: "auto" }}>
      <form onSubmit={handleSubmit}>
        <textarea
          rows={3}
          placeholder="Type something for ElevenLabs to read..."
          value={text}
          onChange={(e) => setText(e.target.value)}
          style={{ width: "100%", padding: "0.5rem" }}
        />
        <button
          type="submit"
          disabled={loading}
          style={{
            marginTop: "0.5rem",
            padding: "0.5rem 1rem",
            background: "#0066ff",
            color: "#fff",
            border: "none",
            cursor: loading ? "wait" : "pointer",
          }}
        >
          {loading ? "Speaking…" : "Speak"}
        </button>
      </form>
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

You can now drop <VoiceButton /> anywhere in your app. Pass a custom voiceId if you have a cloned voice; otherwise the default voice works out of the box.

Handling Voice Cloning

Voice cloning adds a personal touch but requires a few extra steps:

  1. Create a voice on the ElevenLabs dashboard (or via the /v1/voices endpoint). Upload a 30‑second sample of the target speaker.
  2. Capture the returned voice_id.
  3. Store that ID in your app’s config or fetch it from a backend.

Once you have the ID, just hand it to the hook:

<VoiceButton voiceId="my_custom_voice_id" />
Enter fullscreen mode Exit fullscreen mode

Because the API is the same, you don’t need to change any other code. The generated speech will now sound like the cloned speaker.

Testing the UI

Run the dev server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:5173 and you should see a textarea with a “Speak” button. Type something like:

“Hello, Dev community! This is ElevenLabs speaking through React.”

Hit Speak and you’ll hear the audio play back instantly. If you encounter a CORS error, double‑check that your API key is correctly set and that the ElevenLabs endpoint is reachable from your browser.

Tips & Gotchas

Issue Fix
Audio doesn’t play on mobile Safari blocks autoplay without user interaction. Our button click counts as an interaction, but ensure you’re not pre‑loading the audio.
Rate‑limit errors Free tier limits the number of requests per minute. Cache generated URLs when possible, or debounce rapid submissions.
Voice cloning takes time After uploading a sample, ElevenLabs may need a few seconds to process. Poll the voice status endpoint until is_ready is true before using the new voice_id.
Storing the API key For production, proxy the request through a serverless function (e.g., Vercel or Cloudflare Workers) to keep the key secret.

Conclusion

Integrating voice into a web app used to feel like a niche hobby, but with services like ElevenLabs and the simplicity of React hooks, you can add high‑quality speech in just a handful of lines. Whether you’re building an accessibility feature, a chatbot, or a content‑reading tool, the pattern shown here scales: swap out the text, switch the voice ID, and you’re good to go.

Ready to give your UI a voice? Head over to ElevenLabs, grab a free API key, and start experimenting with the code above. Happy coding!

Try ElevenLabs today: https://try.elevenlabs.io/kr07zfuqn1bp.

Top comments (0)