webdevpuneet.com

Karaoke Word Highlight — Timed Lyrics That Fill As Sung

Karaoke Word Highlight · Text Animation · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<main class="stage">
  <div class="player">
    <p class="track">Now playing · Ship It Twice</p>
    <div class="lyrics" id="lyrics" aria-live="off"></div>
    <div class="bar" id="bar" role="progressbar" aria-label="Song progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><span id="fill"></span></div>
    <div class="row">
      <button type="button" id="play" class="play">Play</button>
      <button type="button" id="restart" class="ghost">Restart</button>
      <span class="time" id="time">0:00</span>
    </div>
  </div>
</main>

Extend with AI

Take This Snippet Further With AI

Ask an AI coding assistant to explain why each word's fill is a background-position on a double-width gradient instead of an animated width, and why everything is drawn from one time variable in a render function. A good follow-up is how you would connect it to a real audio element's currentTime and handle seeking.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a karaoke-style synced text highlight in plain HTML, CSS and JavaScript.

Requirements:
- Lyrics defined as a data array of lines, each line a list of [word, startSeconds, lengthSeconds]. JavaScript builds a paragraph per line and a span per word.
- Each word is clipped text with a transparent colour and a two-colour linear-gradient background (bright 50%, dim 50%) sized to 200% width. Slide background-position from 100% to 0% to fill the word as it is sung.
- One render function takes the current time t and sets every word's fill from (t - start) / length clamped to 0..1, toggles a "now" class on the active line (full opacity, normal scale, others dimmed), and updates a progress bar and an m:ss time label.
- A requestAnimationFrame loop adds the frame delta to t while playing; Play/Pause and Restart buttons control it and stop at the end.
- The progress bar has role progressbar with a live aria-valuenow; the lyrics container is not a live region.
- Under prefers-reduced-motion, show all lines lit and skip transitions.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

What's included

Features

Word-level timing
Each word has its own start and length, so fast and slow phrases fill at the right speed.
Position-based fill
A 200%-wide gradient slid with background-position works for any word length without measuring.
One render function
Everything (words, active line, bar, time) is drawn from a single time value, so seeking is a one-line change.
Active line focus
Only the current line is fully bright and normal size, with neighbours dimmed to guide the eye.
Data-driven
Lyrics are a plain array that can be generated from subtitle files or a timing tool.
Accessible controls
Real buttons, a progressbar role with live value, and reduced-motion users see all lines lit with no animation.

About this UI Snippet

Karaoke Word Highlight — Per-Word Timed Gradient Fill Driven by a Clock

Screenshot of the Karaoke Word Highlight snippet rendered live

Karaoke, lyric videos and read-along captions all share one idea: the word you should be saying right now is lit, and the lit area sweeps across it at the pace it's spoken. This snippet builds that from a simple data table, not a video. Each word has a start time and a length, and a single requestAnimationFrame loop turns the elapsed time into a fill amount for every word.

A word is a two-colour gradient you slide

Each word's background is linear-gradient(90deg, light 50%, dim 50%) sized to 200% width and clipped to the text. Moving background-position from 100% to 0% slides the bright half across the word like a wipe. Because the fill is a position, not a width, it works for words of any length and any font with no measuring.

One clock, one render function

The loop adds the delta of each animation frame to t, and render() computes every word's progress as (t − start) / length, clamped between 0 and 1. The same function updates the active line (brightened and scaled a little), the progress bar and the m:ss readout. Pausing just stops requesting frames; restarting sets t to zero. Because the display is a pure function of t, you can bolt on seeking, a scrubber or a real <audio> element by setting t = audio.currentTime.

Data, not markup

Lyrics live in a plain array of [text, start, length], which is easy to generate from subtitle files or a timing tool. The progress bar has a proper progressbar role with a live value, the lyric container isn't a live region (it would be too chatty), and reduced-motion visitors get all lines lit and no animation.

Step by step

How to Use

  1. 1
    Paste the three blocksA dark player card appears with three lyric lines, a progress bar and Play / Restart buttons. Press Play and the words fill in order, with the current line brightened.
  2. 2
    Write your own linesEdit the SONG array. Each line is a list of [word, startSeconds, lengthSeconds]. Keep the start times ascending and update END to the last end time plus a little.
  3. 3
    Sync to real audioAdd an <audio> element and, instead of adding frame deltas to t, set t = audio.currentTime in the loop. Pause and play the audio from the same buttons.
  4. 4
    Recolour the fillChange the two gradient colours on .word for the lit and unlit halves, and the progress bar gradient to match.
  5. 5
    Use it for captionsSwap the lyrics for subtitle text with word timings to get read-along captions for a video or podcast clip.

Real-world uses

Common Use Cases

Music and lyric pages
Artists and labels can show synced lyrics on a track page without hosting a video.
Language learning
Highlight the word being spoken in a dialogue or poem so learners follow along.
Read-along children's content
Follow a story as it is read aloud, one word at a time.
Podcast and video captions
Show a pull-quote or transcript line that lights as it's spoken in a clip.
Event and karaoke-night sites
A playful lyrics demo for bars, parties and quiz-night landing pages.

Got questions?

Frequently Asked Questions

For lyrics, tap them out with a small timing tool or use an audio editor's markers. For speech, transcription services return word-level timestamps you can convert to this array format.

It runs in step with the display and pauses in background tabs. Using the frame delta also avoids drift, because progress is computed from elapsed time, not counted ticks.

Yes: add a click handler on each word that sets t = word.start and calls render(). Because rendering depends only on t, no other state needs fixing.

Yes in all current browsers; Safari needs the -webkit- prefix, which is included. The text colour is transparent so the gradient shows through.