Typed.js Multi-String Hero — Free Typewriter Headline Snippet
Typed.js Multi-String Hero · Text Animation · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<section class="tjh-hero">
<div class="tjh-eyebrow">✦ Now hiring</div>
<h1 class="tjh-heading">We build for<br><span id="tjhTyped" class="tjh-typed"></span><span class="tjh-caret" aria-hidden="true"></span></h1>
<p class="tjh-sub">One product team, five very different customers — the headline cycles through who we ship for, typed and deleted in a loop by Typed.js.</p>
<div class="tjh-actions">
<button class="tjh-btn tjh-primary">See open roles</button>
<button class="tjh-btn tjh-ghost">Our story →</button>
</div>
</section>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0b0a17;color:#eceafd;min-height:100vh}
.tjh-hero{max-width:680px;margin:0 auto;padding:14vh 24px;text-align:center}
.tjh-eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#a5b4fc;background:rgba(99,102,241,.14);border:1px solid rgba(99,102,241,.3);padding:5px 14px;border-radius:20px;margin-bottom:26px}
.tjh-heading{font-size:clamp(32px,6.5vw,58px);font-weight:800;line-height:1.18;letter-spacing:-.02em;margin-bottom:22px;min-height:2.4em}
.tjh-typed{background:linear-gradient(135deg,#818cf8,#e879f9);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.tjh-caret{display:inline-block;width:3px;height:.85em;background:#e879f9;margin-left:4px;vertical-align:-.1em;animation:tjhBlink 1s steps(1) infinite}
@keyframes tjhBlink{50%{opacity:0}}
.tjh-sub{color:#9691c4;font-size:16px;line-height:1.7;max-width:520px;margin:0 auto 34px}
.tjh-actions{display:flex;gap:14px;justify-content:center}
.tjh-btn{padding:13px 26px;border:none;border-radius:10px;font-size:15px;font-weight:600;cursor:pointer;font-family:inherit}
.tjh-primary{background:linear-gradient(135deg,#6366f1,#c026d3);color:#fff;box-shadow:0 4px 20px rgba(99,102,241,.35)}
.tjh-primary:hover{transform:translateY(-2px)}
.tjh-ghost{background:rgba(255,255,255,.06);color:#eceafd;border:1px solid rgba(255,255,255,.14)}
.tjh-ghost:hover{background:rgba(255,255,255,.12)}// Typed.js takes an options object with a strings array and manages the
// entire type-delete-type loop, including the cursor, itself — this is
// the whole animation, no manual setInterval/substring logic required.
const typed = new Typed('#tjhTyped', {
strings: [
'ambitious founders.',
'enterprise teams.',
'solo builders.',
'design partners.',
'the next big idea.',
],
typeSpeed: 48,
backSpeed: 28,
backDelay: 1400,
startDelay: 300,
loop: true,
smartBackspace: true,
showCursor: false, // using our own CSS-animated caret instead of Typed's
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Typed.js Multi-String Hero — Cycling Headlines with a Real Typewriter Loop

A rotating headline is a common way to communicate that one product serves several different audiences without needing five separate hero sections. This snippet uses Typed.js — a small, purpose-built typewriter animation library — to cycle a hero headline through five phrases, each one typed out, held briefly, and deleted before the next begins, all driven by a single strings array.
One options object, a full animation loop
new Typed('#tjhTyped', { strings: [...], typeSpeed: 48, backSpeed: 28, backDelay: 1400, loop: true }) is the entire animation. Typed.js owns the interval-based logic of adding one character at a time, pausing once a string is fully typed, deleting characters at a different (usually faster) speed, and moving to the next string in the array — looping back to the first once the array is exhausted. Compare this to Typewriter or Split Text, which build a single-string typing effect from scratch; this snippet gets the equivalent behavior across an arbitrary number of strings from one constructor call.
Independent type and delete speeds
typeSpeed: 48 and backSpeed: 28 are deliberately different — text usually reads better typing in a bit slower than it deletes, since deletion is meant to feel like a quick "clearing" gesture rather than a mirrored typing animation. backDelay: 1400 is the pause after a string finishes typing and before deletion starts, giving the reader time to actually read the phrase rather than watching it vanish immediately.
smartBackspace and why it matters here
Because two of this hero's phrases share no common prefix while others might, smartBackspace: true tells Typed.js to only delete the characters that differ between the current string and the next one, rather than deleting the entire string down to nothing and retyping from scratch. For phrases that do share a prefix, this produces a subtly smarter, faster transition; when there's no shared prefix (as here) it behaves the same as a full backspace.
A custom CSS caret instead of Typed's own
Typed.js can render its own blinking cursor span, but this snippet sets showCursor: false and supplies a separate .tjh-caret element animated with a plain CSS @keyframes blink instead — this keeps the caret's exact color, width, and blink timing fully controllable in CSS rather than through Typed's cursor options, and avoids any layout shift from Typed inserting and removing its own cursor markup.
Customizing it
Add or remove phrases in the strings array, tune typeSpeed/backSpeed/backDelay for a snappier or more deliberate rhythm, or pair this hero with a Hero Section layout below featuring the specific audience the current phrase describes.
Step by step
How to Use
- 1Add the Typed.js CDN scriptInclude typed.umd.js from the CDN panel.
- 2Paste HTML, CSS, and JSA hero with an empty typed span and blinking caret renders.
- 3Watch the headline typeThe first phrase types out character by character.
- 4Watch it pause, then deleteAfter backDelay, it backspaces before typing the next phrase.
- 5Let it loopAll five phrases cycle continuously since loop is true.
- 6Edit the strings arrayReplace the five phrases with your own audiences or value props.
Real-world uses
Common Use Cases
strings array that supports an unlimited loop.backDelay controlling how long each finished phrase stays visible for reading.typeSpeed and backSpeed tuned independently for each direction.smartBackspace to avoid deleting shared prefixes.Got questions?
Frequently Asked Questions
The strings array is processed in order: Typed.js types out the current string one character at a time at typeSpeed, waits for backDelay once fully typed, deletes it at backSpeed, then advances to the next string in the array. Once the last string has been typed, deleted, and loop is true, it restarts from the first string, creating a continuous cycle.
Without it, Typed.js deletes a finished string down to zero characters before typing the next one from scratch. With smartBackspace: true, if the next string in the array shares a prefix with the current one, Typed.js only deletes the differing suffix and types the new suffix — producing a faster, subtly smarter-feeling transition for phrases that share common wording.
Typed.js can render and animate its own cursor element automatically, but this snippet opts out of that (showCursor: false) and instead renders a separate .tjh-caret span animated with a plain CSS @keyframes blink. This gives full control over the caret's exact width, color, and blink timing directly in CSS rather than through Typed's more limited cursor styling options.
Yes — instead of constructing new Typed(...) immediately on page load, wrap the construction in an IntersectionObserver callback watching the hero section, and only call new Typed('#tjhTyped', {...}) once the hero becomes visible. This defers the animation start until a visitor actually scrolls to it.
Install the typed.js npm package (or keep the CDN script), import Typed, and construct new Typed(selector, options) inside a mount effect (useEffect, onMounted, or ngAfterViewInit) targeting a ref to the span element. Call the returned instance's .destroy() method in the cleanup function to stop the typing loop and remove its DOM manipulation when the component unmounts.