speechtype
Typography that
follows your voice.
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
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
| Option | Default | Description |
|---|---|---|
| activeTracking | 0.06 | Letter-spacing on the active word in em. |
| activeWeight | 700 | wght axis value on the active word. |
| activeOpsz | 24 | opsz axis value on the active word. |
| inactiveOpacity | 0.45 | Opacity of words not currently spoken. |
| transitionMs | 80 | CSS transition duration in ms for style changes. |
| rate | 0.9 | Speech rate (0.1–10). Accepted by all paths; speech options (rate, pitch, volume) are silently ignored by SpeechTypeText and useSpeechType. |
| pitch | 1 | Speech pitch (0–2). Silently ignored by SpeechTypeText and useSpeechType. |
| volume | 1 | Speech volume (0–1). Silently ignored by SpeechTypeText and useSpeechType. |
| onUnsupported | — | Called when the browser does not support SpeechSynthesis. |
| onError | — | Called 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 Settings → Custom Code → Footer, 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>