Source Code

<section class="trh-hero">
  <div class="trh-inner">
    <span class="trh-pill">typed.js · smart backspace</span>

    <h1 class="trh-title">
      We build
      <span class="trh-typed"><span id="trhTarget"></span></span>
    </h1>

    <p class="trh-caption" id="trhCaption">Component libraries that survive a rebrand.</p>

    <div class="trh-actions">
      <button class="trh-cta">Book a call</button>
      <button class="trh-ghost" id="trhToggle">Pause</button>
    </div>

    <div class="trh-speeds" id="trhSpeeds">
      <button class="trh-chip" data-speed="90">Slow</button>
      <button class="trh-chip is-on" data-speed="55">Normal</button>
      <button class="trh-chip" data-speed="22">Fast</button>
    </div>
  </div>
</section>

Typed.js Rotating Headline — Hero Typewriter Snippet

Typed.js Rotating Headline · Heroes · Plain HTML, CSS & JS · Live preview

What's included

Features

Smart backspace
Rewinds only to where consecutive strings diverge.
Index-synced UI
preStringTyped drives accent color and caption per line.
One variable, five elements
A single --accent custom property retints the whole hero.
Cross-faded captions
Opacity out, text swap, opacity in — no mid-line flicker.
Reflow-safe headline
The rotating phrase is display:block so static text never rewraps.
Derived backspace speed
Deleting runs at 55% of type speed so rotations never drag.
Terminal-accurate cursor
A hard-edged blink keyframe on the injected .typed-cursor.
WCAG pause control
stop() and start() satisfy the pause requirement for looping motion.

About this UI Snippet

Typed.js Rotating Headline — Typewriter Text That Drives the Whole Hero

Screenshot of the Typed.js Rotating Headline snippet rendered live

A rotating headline is the most common hero pattern on the web and one of the easiest to do badly. The version that hurts conversion types one character at a time forever, jumps the layout on every wrap, and leaves screen readers reading a stream of half-words. This snippet fixes each of those and adds the thing most implementations miss entirely: the rest of the hero reacts to which line is being typed.

Smart backspace, the feature worth the dependency

You could write a typewriter in thirty lines. The reason to reach for Typed.js is smartBackspace, and it is genuinely hard to reimplement well:

smartBackspace: true

With it off, every rotation deletes the entire line character by character and retypes the next one from nothing. With it on, Typed.js diffs consecutive strings and rewinds only to the point where they differ. The first two strings here are chosen to demonstrate it exactly:

- design systems that scale. - design systems that ship on Friday.

The shared prefix "design systems that " is never deleted. The line rewinds to that point and continues, which is both dramatically faster and reads as an idea being *revised* rather than erased. Writing your rotating strings to share prefixes is a copywriting decision that this one option turns into a visual one.

Syncing the rest of the hero

The preStringTyped callback fires with the array index of the string about to be typed, before typing starts — which makes it the right hook for changing anything that should already be correct when the new line begins:

preStringTyped: applyMeta

applyMeta(i) writes a new accent color to a CSS custom property on the hero root (hero.style.setProperty('--accent', m.accent)) and swaps the caption underneath. Because --accent is consumed by the pill, the typed text, the CTA background, and the active chip border, one property assignment retints five elements, each with its own CSS transition so they ease rather than snap.

The caption swap is handled with a two-step fade rather than a hard text replacement: a .fade class drops opacity, the text is replaced 220ms later inside a setTimeout, then the class is removed. Replacing the text instantly while the line is still visible produces a jarring flicker that undercuts the smoothness of everything else.

Layout stability

.trh-typed { display: block } puts the rotating phrase on its own line. This is deliberate: an inline rotating span changes the width of the headline on every character, which can rewrap the static text before it and cause visible reflow on every keystroke. Giving it a dedicated line means only that line's width changes.

.trh-caption { min-height: 24px } reserves the caption's space so a shorter line does not collapse the block and shift the buttons upward.

The cursor

Typed.js injects its own .typed-cursor element, which is why the CSS styles a class it never declares in the HTML. The default blink is a simple opacity toggle; the trhBlink keyframe here holds the cursor solid for 45% of the cycle and hidden for 45% with sharp transitions, which matches how a real terminal cursor behaves far better than a smooth fade. cursorChar: '▌' swaps the default pipe for a block character that reads better at display sizes.

Rebuilding for speed changes

Typed.js reads typeSpeed once at construction, so changing speed means a new instance — and if (typed) typed.destroy() must come first. Without it, the old instance keeps its interval running and writes to the same element, so two typewriters fight over one span and produce interleaved garbage. destroy() clears the timers and removes the injected cursor.

Note that backSpeed is derived rather than fixed: Math.max(10, Math.round(speed * 0.55)). Deleting should always feel faster than typing — a rotation that erases at the same speed it writes feels twice as slow as it is.

Pause, and why it matters

The pause button calls typed.stop() and typed.start(). This is not decoration: WCAG 2.2.2 requires that any automatically moving content lasting more than five seconds can be paused. A permanently looping headline qualifies, so shipping one without a pause control is an accessibility failure, not a missing nice-to-have.

Reusing it

Rewrite STRINGS and the matching META array — keep them the same length, since applyMeta indexes straight into it. Write your strings to share leading words wherever the meaning allows, so smartBackspace has something to work with. For a static alternative without the dependency, see typewriter, or word flip hero when you want whole words to swap rather than type.

Build with AI

Build, Understand, Optimize, and Extend It With AI

The genuinely interesting parts of this hero are the callback wiring and one option, so it is worth having explained rather than skimmed. Paste the HTML, CSS, and JS into an AI assistant like Claude and ask it to explain exactly how smartBackspace decides where to stop rewinding between two consecutive strings, then have it rewrite the STRINGS array so that no two entries share a prefix and describe how the rotation would feel differently. Ask why applyMeta is wired to preStringTyped rather than onStringTyped, and what would visibly change if it fired after the line finished instead of before it started. For optimization, ask whether the 220ms setTimeout inside applyMeta can desynchronize from the CSS transition duration and how you would tie them together properly. To extend it: have it pause the loop when the hero scrolls out of view with an IntersectionObserver, respect prefers-reduced-motion by showing a single static line, drive STRINGS from a CMS, or add an aria-live region so assistive tech announces each completed line rather than every keystroke. Treat the code less like a finished artifact and more like a starting point for a conversation.

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 marketing hero with a rotating typewriter headline using Typed.js (v2 UMD from a CDN, global Typed) in plain HTML, CSS, and JavaScript.

Requirements:
- Rotate through about five value-proposition strings, and deliberately write at least two of them to share a leading phrase (for example "design systems that scale." and "design systems that ship on Friday.") so that smartBackspace: true has something to demonstrate — it must rewind only to the point where consecutive strings differ rather than deleting the whole line.
- Maintain a parallel META array, one entry per string, each holding an accent color and a caption. Use the preStringTyped callback — which fires with the array index BEFORE that string starts typing — to apply the matching entry, so the surrounding UI is already correct when the new line begins.
- Applying a meta entry must write its color to a single --accent CSS custom property on the hero root, and the pill, the typed text, the CTA button background and the active speed chip border must all consume that one variable, each with its own CSS transition, so one assignment retints the whole hero smoothly.
- Swap the caption with a two-step cross-fade rather than replacing text instantly: add a fade class to drop opacity, replace the text after a short timeout, then remove the class — a hard swap while the old line is visible flickers.
- Put the rotating phrase on its own line with display: block, and explain why: an inline rotating span changes the headline width every keystroke, which can rewrap the static text and cause visible reflow. Also give the caption a min-height so a shorter line does not collapse the block and shift the buttons.
- Style the cursor that Typed.js injects (the .typed-cursor class it creates itself) with a hard-edged blink keyframe that holds solid then hidden, rather than a smooth fade, and set cursorChar to a block character.
- Provide Slow / Normal / Fast speed buttons that rebuild the Typed instance, calling destroy() on the previous one FIRST — explain that Typed.js reads typeSpeed only at construction and that skipping destroy leaves the old timers running so two typewriters interleave characters into the same span. Derive backSpeed as roughly 55% of typeSpeed (floored around 10ms) so deleting always feels faster than typing.
- Include a Pause/Resume button calling typed.stop() and typed.start(), and note that WCAG 2.2.2 requires automatically moving content lasting over five seconds to be pausable, so this control is a requirement rather than an extra.

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.

Step by step

How to Use

  1. 1
    Add the Typed.js CDNInclude the typed.umd build from the CDN panel — global Typed.
  2. 2
    Paste HTML, CSS, and JSThe headline starts rotating through five value propositions.
  3. 3
    Watch the rewindLines sharing a prefix rewind only to the difference, not to empty.
  4. 4
    Notice the hero retintAccent color and caption change with each new line.
  5. 5
    Change the speedSlow, Normal, and Fast rebuild the instance with a derived backspace speed.
  6. 6
    Write your own linesEdit STRINGS and META together — they are indexed in parallel.

Real-world uses

Common Use Cases

Agency and SaaS heroes
Rotate through what you actually sell above the fold.
Portfolio landings
Cycle roles or disciplines with the accent following each.
Feature carousels
Pair with a word flip hero for a quieter variant.
Waitlist and launch pages
Type the benefit above a waitlist signup.
Job and hiring pages
Rotate the roles you are hiring for with a color per team.
Learning typewriter UX
A reference for smart backspace and callback-driven UI sync.
Related: Hero with Terminal Boot Sequence
See the Hero with Terminal Boot Sequence for a related heroes pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It diffs the current string against the next one and rewinds only to the point where they differ, instead of deleting the whole line. With strings like "design systems that scale." followed by "design systems that ship on Friday.", the shared prefix is never erased — the rotation is faster and reads as a revision rather than a restart.

The preStringTyped callback receives the index of the string about to be typed. It writes the matching accent color to a --accent custom property on the hero root, and because the pill, typed text, CTA background, and active chip border all consume that variable, one assignment retints five elements — each easing via its own CSS transition.

An inline rotating span changes the headline width on every character, which can rewrap the static text before it and cause visible reflow on each keystroke. Setting the typed span to display: block confines width changes to that line alone.

Typed.js reads typeSpeed once at construction, so a speed change needs a new instance. destroy() must be called first — otherwise the old instance keeps its timers running against the same element and two typewriters interleave characters into one span.

Deleting should always feel quicker than writing; a rotation that erases at the same rate it types feels about twice as slow as it is. Deriving it as roughly 55% of the type speed, floored at 10ms, keeps that ratio correct at every speed setting.

Create the Typed instance in a mount effect against a ref to the target span, and call destroy() in cleanup so a remount does not leave a running timer writing into a detached node. Hold the accent and caption in state and set them from the preStringTyped callback rather than mutating the DOM. Rebuild the instance in an effect keyed to the speed value.