Voiceprint

Audio Waveform Background for React

A background that prints a voice as it speaks. Sound comes in at the middle as a stroke of dashes as tall as it is loud, then travels out to both edges, so a sentence spreads across the section like a pressed waveform. Each speaker prints in their own color, and in silence it settles back into a calm dot grid.

  • Background
  • AI
  • Canvas

Uses clsx, tailwind-merge

Open in ChatGPTOpen in Claude
speed90px/s
spacing10px
reach0.90
attack0.03s
release0.14s
falloff0.60
<Voiceprint />

The demo follows as you drag.

The front desk that picks up on the first ring

A scripted call: Marlow, a voice agent, books a same-day appointment for a patient with a chipped tooth.

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Voiceprint: A background that prints a voice as it speaks. Sound comes in at the middle as a stroke of dashes as tall as it is loud, then travels out to both edges, so a sentence spreads across the section like a pressed waveform. Each speaker prints in their own color, and in silence it settles back into a calm dot grid.
This demo, Voice Agent Hero: A clinic's voice agent books a same-day appointment on a looping call, agent in blue and patient in ink, and Talk to it prints your own voice from the microphone.

1. Install it:
   npx shadcn@latest add @keyframeui/voiceprint-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/voiceprint-demo.json instead.)
   It adds the demo, components/voiceprint-demo.tsx, and the component, components/ui/voiceprint.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <Voiceprint> on its own.

3. These props set its feel. Change them before you change the file:
   - speed (default 90): How fast what was said travels out to the edges, in px per second.
   - spacing (default 10): Distance between dots, in px.
   - reach (default 0.9): How far the loudest sound reaches from the middle line, as a share of half the height. Lower it to keep a headline above the print clear.
   - attack (default 0.03): How quickly the level rises to a new sound, in seconds (a time constant). Longer softens the start of each stroke.
   - release (default 0.14): How quickly the level falls away after a sound, in seconds (a time constant). Longer smears syllables into one another; shorter prints each as its own stroke.
   - falloff (default 0.6): Share of its ink a sound has lost by the time it reaches the edge, from 0 (none) to 1 (all of it).

4. Don't change, in components/ui/voiceprint.tsx:
   - Every frame runs in `createPrint`, outside React: one rAF loop draws to a canvas and reads a function `level` each frame, and it sleeps once the last sound has left both edges, off screen, or in a hidden tab.
   - Nothing moves: each column shows the loudest history sample from as long ago as sound takes to reach it at `speed`, so sound travels out mirrored, and each sample keeps its tone.
   - Marks are batched into one stroke per tone and opacity step, and the resting grid is drawn once per size and color, then copied under every frame.
   - A `source` runs through a noise gate (a floor that drops fast and creeps up slowly, shut while it warms up), so tape hiss and room tone never print.
   - Reduced motion keeps the print still: a band around the middle takes the speaker's color by opacity alone, read from a `useSyncExternalStore` flag that's false while hydrating.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/voiceprint.md

Installation

Add the component on its own with the CLI, or copy the file in by hand.

The CLI writes components/ui/voiceprint.tsx and installs clsx and tailwind-merge. It needs the @keyframeui registry in components.json; add it once per project.

npx shadcn@latest add @keyframeui/voiceprint

Examples

Finished sections built with Voiceprint. Each one installs whole, with the component included.

Recording Player

A transcription player for NASA's Apollo 11 tapes, where the print listens to the audio element itself and the tape's hiss stays under the noise gate.

Every word on the tape, pinned to its second

First step

Apollo 11 · 21 Jul 1969, 02:56 UTC

0:00 / 0:24

0:03

Neil Armstrong

I'm going to step off the LM now.

Transcribed by Quillmark · Audio: NASA, public domain

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Voiceprint: A background that prints a voice as it speaks. Sound comes in at the middle as a stroke of dashes as tall as it is loud, then travels out to both edges, so a sentence spreads across the section like a pressed waveform. Each speaker prints in their own color, and in silence it settles back into a calm dot grid.
This demo, Recording Player: A transcription player for NASA's Apollo 11 tapes, where the print listens to the audio element itself and the tape's hiss stays under the noise gate.

1. Install it:
   npx shadcn@latest add @keyframeui/voiceprint-player-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/voiceprint-player-demo.json instead.)
   It adds the demo, components/voiceprint-player-demo.tsx, and the component, components/ui/voiceprint.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <Voiceprint> on its own.

3. These props set its feel. Change them before you change the file:
   - speed (default 90): How fast what was said travels out to the edges, in px per second.
   - spacing (default 10): Distance between dots, in px.
   - reach (default 0.9): How far the loudest sound reaches from the middle line, as a share of half the height. Lower it to keep a headline above the print clear.
   - attack (default 0.03): How quickly the level rises to a new sound, in seconds (a time constant). Longer softens the start of each stroke.
   - release (default 0.14): How quickly the level falls away after a sound, in seconds (a time constant). Longer smears syllables into one another; shorter prints each as its own stroke.
   - falloff (default 0.6): Share of its ink a sound has lost by the time it reaches the edge, from 0 (none) to 1 (all of it).

4. Don't change, in components/ui/voiceprint.tsx:
   - Every frame runs in `createPrint`, outside React: one rAF loop draws to a canvas and reads a function `level` each frame, and it sleeps once the last sound has left both edges, off screen, or in a hidden tab.
   - Nothing moves: each column shows the loudest history sample from as long ago as sound takes to reach it at `speed`, so sound travels out mirrored, and each sample keeps its tone.
   - Marks are batched into one stroke per tone and opacity step, and the resting grid is drawn once per size and color, then copied under every frame.
   - A `source` runs through a noise gate (a floor that drops fast and creeps up slowly, shut while it warms up), so tape hiss and room tone never print.
   - Reduced motion keeps the print still: a band around the middle takes the speaker's color by opacity alone, read from a `useSyncExternalStore` flag that's false while hydrating.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/voiceprint.md

Dubbing Preview

Each line plays in English in ink, then as an AI dub in orange on the same beats, so the same shape prints twice; the chips pick the language.

Your voice in 32 languages, on the same beat

A looping preview: each line of a woodworking video plays in English, then as Mynah's Spanish dub in the same voice and rhythm.

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Voiceprint: A background that prints a voice as it speaks. Sound comes in at the middle as a stroke of dashes as tall as it is loud, then travels out to both edges, so a sentence spreads across the section like a pressed waveform. Each speaker prints in their own color, and in silence it settles back into a calm dot grid.
This demo, Dubbing Preview: Each line plays in English in ink, then as an AI dub in orange on the same beats, so the same shape prints twice; the chips pick the language.

1. Install it:
   npx shadcn@latest add @keyframeui/voiceprint-dubbing-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/voiceprint-dubbing-demo.json instead.)
   It adds the demo, components/voiceprint-dubbing-demo.tsx, and the component, components/ui/voiceprint.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <Voiceprint> on its own.

3. These props set its feel. Change them before you change the file:
   - speed (default 90): How fast what was said travels out to the edges, in px per second.
   - spacing (default 10): Distance between dots, in px.
   - reach (default 0.9): How far the loudest sound reaches from the middle line, as a share of half the height. Lower it to keep a headline above the print clear.
   - attack (default 0.03): How quickly the level rises to a new sound, in seconds (a time constant). Longer softens the start of each stroke.
   - release (default 0.14): How quickly the level falls away after a sound, in seconds (a time constant). Longer smears syllables into one another; shorter prints each as its own stroke.
   - falloff (default 0.6): Share of its ink a sound has lost by the time it reaches the edge, from 0 (none) to 1 (all of it).

4. Don't change, in components/ui/voiceprint.tsx:
   - Every frame runs in `createPrint`, outside React: one rAF loop draws to a canvas and reads a function `level` each frame, and it sleeps once the last sound has left both edges, off screen, or in a hidden tab.
   - Nothing moves: each column shows the loudest history sample from as long ago as sound takes to reach it at `speed`, so sound travels out mirrored, and each sample keeps its tone.
   - Marks are batched into one stroke per tone and opacity step, and the resting grid is drawn once per size and color, then copied under every frame.
   - A `source` runs through a noise gate (a floor that drops fast and creeps up slowly, shut while it warms up), so tape hiss and room tone never print.
   - Reduced motion keeps the print still: a band around the middle takes the speaker's color by opacity alone, read from a `useSyncExternalStore` flag that's false while hydrating.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/voiceprint.md

Props

children

TypeReact.ReactNode

The section's content, drawn over the print.

level

Typenumber | (() => number)Default0

Loudness now, from 0 to 1. Pass a function to have it read every frame, so a live level never re-renders your component.

source

TypeHTMLMediaElement | MediaStream | nullDefaultnull

An <audio> or <video> element, or a MediaStream (a microphone, a call), to listen to. While it's given, its loudness replaces level. A cross-origin file reads as silence unless it's served with CORS.

tone

Type"accent" | "ink"Default"accent"

Who is speaking now: the accent color or the ink color. Sound already printed keeps the color it was spoken in.

speed

TypenumberDefault90

How fast what was said travels out to the edges, in px per second.

spacing

TypenumberDefault10

Distance between dots, in px.

reach

TypenumberDefault0.9

How far the loudest sound reaches from the middle line, as a share of half the height. Lower it to keep a headline above the print clear.

attack

TypenumberDefault0.03

How quickly the level rises to a new sound, in seconds (a time constant). Longer softens the start of each stroke.

release

TypenumberDefault0.14

How quickly the level falls away after a sound, in seconds (a time constant). Longer smears syllables into one another; shorter prints each as its own stroke.

falloff

TypenumberDefault0.6

Share of its ink a sound has lost by the time it reaches the edge, from 0 (none) to 1 (all of it).

paused

TypebooleanDefaultfalse

Hold the print still where it is and stop reading the level.

inkClassName

TypestringDefault"text-zinc-900 dark:text-zinc-100"

A text-* color for the resting dots and the ink speaker. The canvas reads it, so dark: variants work.

accentClassName

TypestringDefault"text-[#2747ff] dark:text-[#6b84ff]"

A text-* color for the accent speaker.

className

Typestring

Extra classes for the root. Give it a size; the print fills it.