speechtype

Typography that
follows your voice.

npm ↗
GitHub ↗
TypeScriptZero dependenciesReact + Vanilla JS

When text is read aloud via speech synthesis, there is no visual indicator of which word is being spoken — the connection between audio and typography is severed. speechType wraps words in spans and listens to Web Speech API boundary events, applying typographic emphasis to each word as it is spoken.

Live demo — step with the Word slider or press Speak if supported

Word
Active weight700
Inactive opacity0.45
Rate0.9×
Transition80ms

Every word spoken carries its own weight — a breath of meaning that arrives, lingers, and fades. To read aloud is to make language visible in time, each syllable a footprint left in the air. The voice knows where it is. The page does not.

Press Speak to hear the paragraph read aloud — each word is emphasised as it is spoken. Use the Word slider to step through manually. Useful for accessibility tools, language learning, teleprompters, and any interface where voice and text need to stay connected.

How it works

The audio-text gap

When the browser speaks text via SpeechSynthesisUtterance, words are heard but not seen. There is no CSS property that tracks synthesis progress — the visual and the auditory are decoupled by design. speechType closes that gap.

Boundary events drive emphasis

The Web Speech API fires boundary events as each word begins. speechType maps the reported character index to the correct word span and applies typographic emphasis — wider tracking, heavier weight, larger optical size — while dimming all other words.

Imperative for performance

During active speech, startSpeechType handles each boundary event directly — updating span styles without touching React state or triggering re-renders. The boundary event fires, the span style changes, and the frame is painted. No scheduling, no batching delay.

React or vanilla JS

SpeechTypeText and useSpeechType manage span preparation and React-driven emphasis. startSpeechType is the vanilla JS entry point — it wraps, speaks, and syncs, returning a stop function.

Usage

TypeScript + React · Vanilla JS

Drop-in component

import { SpeechTypeText, startSpeechType } from '@liiift-studio/speechtype'
import { useRef, useState } from 'react'

// Drive activeWordIndex via startSpeechType or your own state
const [activeWordIndex, setActiveWordIndex] = useState(-1)
const ref = useRef<HTMLParagraphElement>(null)

// startSpeechType updates the spans imperatively during speech:
<SpeechTypeText ref={ref} activeWordIndex={activeWordIndex}>
  The voice knows where it is. The page does not.
</SpeechTypeText>

Start speech synthesis and sync emphasis

import { startSpeechType } from '@liiift-studio/speechtype'

const el = document.querySelector('p')
const stop = startSpeechType(el, { rate: 0.9, activeWeight: 700 })

// Later — cancel speech and restore styles:
// stop()

Hook — manual control with your own active index

import { useSpeechType } from '@liiift-studio/speechtype'
import { useRef } from 'react'

// Inside a React component:
const ref = useRef(null)
useSpeechType(ref, activeWordIndex, { inactiveOpacity: 0.4 })
return <p ref={ref}>Every word spoken carries its own weight.</p>

Options

OptionDefaultDescription
activeTracking0.06Letter-spacing on the active word in em.
activeWeight700wght axis value on the active word.
activeOpsz24opsz axis value on the active word.
inactiveOpacity0.45Opacity of words not currently spoken.
transitionMs80CSS transition duration in ms for style changes.
rate0.9Speech rate (0.1–10). Accepted by all paths; speech options (rate, pitch, volume) are silently ignored by SpeechTypeText and useSpeechType.
pitch1Speech pitch (0–2). Silently ignored by SpeechTypeText and useSpeechType.
volume1Speech volume (0–1). Silently ignored by SpeechTypeText and useSpeechType.
onUnsupportedCalled when the browser does not support SpeechSynthesis.
onErrorCalled with the SpeechSynthesisErrorEvent if synthesis errors.
as'p'HTML element to render. (SpeechTypeText only)

no-code

Use it in Webflow

The same effect, no build step — drop it straight into your design tool.

Webflow

One script tag, then mark any element with data-speechtype. Configure it with data-* attributes.

<!-- Site SettingsCustom CodeFooter, or an Embed element -->
<script src="https://cdn.jsdelivr.net/npm/@liiift-studio/speechtype/dist/speechtype.webflow.min.js"></script>

<!-- Then add data-speechtype to any text element -->
<h1 data-speechtype>Your headline</h1>