Source Code

<div class="scene">
  <p class="sub">Hi, I'm a developer who</p>
  <h1>
    builds
    <span class="typed-wrap">
      <span id="typed"></span><span class="cursor">|</span>
    </span>
  </h1>
</div>

Typewriter Effect — Free HTML CSS JS Snippet

Typewriter Effect · Animations · Plain HTML, CSS & JS · Live preview

What's included

Features

words array cycles infinitely via wi = (wi + 1) % words.length
tick() state machine: deleting boolean switches between type and erase modes
80ms per typed character, 50ms per erased character — faster erase than type
1800ms pause at full word completion before starting erase
CSS blink cursor: animation: blink 0.7s step-end infinite — hard on/off flash
clamp() font-size scales headline without media queries
Zero dependencies — setTimeout recursive loop, no library needed
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons
Live split-pane editor — preview updates as you type

About this UI Snippet

Typewriter Effect — words Array, tick() State Machine & CSS Blink Cursor

Screenshot of the Typewriter Effect snippet rendered live

The typewriter effect types words character by character, pauses at completion, erases character by character, then moves to the next word. Combined with a CSS blinking cursor, it creates the impression of a live terminal or a human typing. Used on hero sections (see the word-flip hero) to cycle through product values, personas, or use cases without static text — for a decode-style reveal instead, see the text scramble.

The tick() state machine

tick() is a recursive function driven by setTimeout. It has two states controlled by the deleting boolean. In the typing state (!deleting): el.textContent = word.slice(0, ++ci) adds one character per tick at 80ms. When ci === word.length the word is complete — deleting = true and a 1800ms pause happens before the next tick. In the erasing state (deleting): el.textContent = word.slice(0, --ci) removes one character per tick at 50ms (faster erase than type). When ci === 0, wi = (wi + 1) % words.length advances to the next word and deleting = false.

The words array

const words = ['fast UIs', 'clean code', 'great DX', ...] — replace with your own phrases. The array loops infinitely via modulo. Shorter words erase faster; longer words have more visible typing time.

The CSS blink cursor

The .cursor element is a | character with animation: blink 0.7s step-end infinite. step-end` makes the cursor snap on/off instantly rather than fading — matching the hard blink of a real terminal cursor.

The character-by-character typing loop

type() appends one character per call using setInterval at a speed of typically 80-100ms per character. It reads from the current word in the words array at the current charIndex position. When charIndex equals the word length, the typing phase ends and a pause begins before erasing starts. erase() decrements charIndex, removing the last character each interval at a faster speed (40-50ms). When charIndex reaches 0, the word index advances and the next word begins typing.

The blinking cursor

A ::after pseudo-element on .typewriter-text with content: '|' or a separate .cursor element blinks via a CSS keyframe: @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }. The cursor animates continuously but should be paused during typing: animation-play-state: paused on the .typing class. This matches how real terminal cursors behave — blinking when waiting, solid when typing.

Infinite loop and word cycling

The words array loops: when the last word finishes typing and erasing, the word index resets to 0. The loop repeats indefinitely. To pause at specific words, add a custom pauseTime property per word and use setTimeout instead of the interval for that word's display duration.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Instead of tracing the timing constants by hand, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the deleting boolean turns a single recursive tick() function into two distinct state machines sharing one code path, and why the erase speed (50ms) is deliberately faster than the type speed (100ms). It's also a good target for an optimization question — ask whether chaining setTimeout calls indefinitely could ever leak if the component unmounts mid-word, and how you'd guard against that. For extending it, have it add per-word pause durations instead of one fixed 1800ms hold, support typing full sentences with word-wrap-aware erase timing, or pause the cycle entirely when the tab is hidden using the Page Visibility API. 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 multi-word typewriter effect in plain HTML, CSS, and vanilla JavaScript with no libraries, driven by a single recursive function and setTimeout — no setInterval.

Requirements:
- A words array of phrases to cycle through indefinitely, plus a current word index, a current character index, and a boolean flag for whether the effect is currently deleting or typing.
- A single tick() function that branches on the deleting flag: when not deleting, it must slice the current word up to one more character than last time and write it into the target element's text content; when the full word length is reached, it must flip to deleting mode and wait a longer pause (roughly 1800ms) before continuing.
- When deleting, tick() must slice the current word down by one character each call; when the slice reaches zero length, it must advance to the next word index using modulo wraparound so the list loops forever, flip back to typing mode, and continue after a short pause.
- Typing characters must use a slower per-character delay than deleting characters, so erasing visibly happens faster than typing.
- A separate blinking cursor element next to the typed text must blink with a CSS keyframe animation using a hard step (not a smooth fade), so it snaps on and off like a real terminal cursor.
- The whole loop must be self-starting on page load and require no user interaction to begin cycling through the words array.

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
    Watch the effect in the previewThe typewriter cycles through words, pausing 1.8s at each full word before erasing and typing the next.
  2. 2
    Update the words arrayIn the JS panel, update the words array with your own phrases — product values, user roles, or features.
  3. 3
    Change the fixed prefix textIn the HTML panel, update the static "Build" text that precedes the animated word.
  4. 4
    Change typing and erasing speedIn the JS panel, update 80 (ms per typed character) and 50 (ms per erased character) in the setTimeout calls.
  5. 5
    Change the pause durationUpdate 1800 in the setTimeout that fires when the full word is typed.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Hero headline dynamic keyword cycling
Cycle through product values, user personas, or features in a hero headline: "Build [fast UIs / clean APIs / great DX]". Keeps the headline fresh without needing separate pages.
Portfolio tagline animation
Show your skills cycling in a typewriter: "I build [React apps / REST APIs / pixel-perfect UIs]". More engaging than a static tagline.
Learn setTimeout state machines
The typewriter uses a recursive tick() function with two states. Edit the typing and erasing speeds in the JS panel to understand how the state machine controls timing.
Loading and processing indicators
Use a typewriter to type status messages during a multi-step process: "Compiling...", "Optimising...", "Done!" with appropriate timing per step.
Terminal-style developer interfaces
Combine with the Aurora Background and the blinking cursor for a terminal-aesthetic hero that signals a developer-focused product.
A/B test headline variants
The words array is trivial to change. Test different keyword orders, phrase lengths, and value propositions without code changes.

Got questions?

Frequently Asked Questions

tick() checks the deleting state. When typing: el.textContent = word.slice(0, ++ci) adds one character. When ci equals word length, deleting = true and a 1800ms timeout fires. When erasing: ci decrements until 0, then wi advances via modulo and deleting resets to false.

The setTimeout in the typing branch uses 80ms per character. The erasing branch uses 50ms. Reduce these values (e.g. 40ms/30ms) for faster animation, or increase (120ms/80ms) for slower.

Remove the animation: blink ... property from .cursor in the CSS panel. The cursor will remain visible without blinking.

Yes. Add longer strings to the words array: "build delightful web experiences" is valid. Longer strings take more time to type and erase — adjust the 1800ms pause if needed.

Remove the modulo wraparound: instead of wi = (wi + 1) % words.length, use if (wi < words.length - 1) wi++; else { deleting = false; return; } to stop after the last word.

Yes. Click "JSX" for a React component. In React, manage ci, wi, and deleting in useState. Use useEffect with a setTimeout to call tick(), storing the timeout ID in useRef for cleanup on unmount.