14 CSS Typewriter Effect Designs 03 / 14

CSS Typewriter Word Swap Loop

A hero headline cycles through swappable highlight words using a single CSS keyframe that animates width and color on a looping overflow-hidden container.

Pure CSS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="tw-03">
  <div class="tw-03__hero">
    <h1 class="tw-03__headline">
      We design
      <span class="tw-03__slot">
        <span class="tw-03__words">
          <span>websites</span>
          <span>products</span>
          <span>systems</span>
          <span>futures</span>
          <span>websites</span>
        </span>
      </span>
    </h1>
    <p class="tw-03__sub">Digital craftsmanship for ambitious brands.</p>
  </div>
</div>
.tw-03, .tw-03 *, .tw-03 *::before, .tw-03 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.tw-03 ::selection { background: #f97316; color: #1a0800; }

.tw-03 {
  --orange: #f97316;
  --amber: #fbbf24;
  --bg: #0c0802;
  --text: #fef3c7;
  --muted: #92400e;
  font-family: 'Georgia', serif;
  min-height: 340px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
}

.tw-03__hero {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}

.tw-03__headline {
  font-size: clamp(1.8rem, 5vw, 3.2rem);
  color: var(--text);
  font-weight: 300;
  line-height: 1.3;
  display: flex;
  align-items: baseline;
  gap: 0.3em;
  flex-wrap: wrap;
  justify-content: center;
}

.tw-03__slot {
  display: inline-block;
  overflow: hidden;
  height: 1.2em;
  vertical-align: bottom;
  border-right: 3px solid var(--orange);
  background: linear-gradient(135deg, rgba(249,115,22,0.15), rgba(251,191,36,0.08));
  border-radius: 4px;
  padding: 0 6px;
  animation:
    tw-03-width 8s steps(1) infinite,
    tw-03-blink 0.65s steps(2) infinite;
}

.tw-03__words {
  display: flex;
  flex-direction: column;
  animation: tw-03-scroll 8s steps(4) infinite;
}

.tw-03__words span {
  font-style: italic;
  font-weight: 600;
  color: var(--orange);
  flex-shrink: 0;
  height: 1.2em;
  line-height: 1.2;
  white-space: nowrap;
}

.tw-03__sub {
  font-size: 1rem;
  color: var(--muted);
  letter-spacing: 0.05em;
}

@keyframes tw-03-scroll {
  0%   { transform: translateY(0); }
  25%  { transform: translateY(-1.2em); }
  50%  { transform: translateY(-2.4em); }
  75%  { transform: translateY(-3.6em); }
  100% { transform: translateY(0); }
}

@keyframes tw-03-width {
  0%,100% { width: 7.5ch; }
  25%     { width: 8ch; }
  50%     { width: 7.5ch; }
  75%     { width: 7.5ch; }
}

@keyframes tw-03-blink {
  0%,100% { border-color: var(--orange); }
  50%     { border-color: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .tw-03__slot { animation: none; border-right-color: var(--orange); }
  .tw-03__words { animation: none; }
}

How this works

The rotating word lives inside a fixed-height overflow: hidden slot. A vertical stack of spans is translated upward via transform: translateY() in keyframe steps, creating the illusion of a new word rolling in from below. Simultaneously, the container's width keyframe expands and contracts between the ch values of each word so the highlight box snugly fits every word at the right moment.

The cursor blinks via a CSS border-right animation scoped to the container element. Because both animations share the same duration and loop infinitely, the timing stays locked: the word swap and the cursor blink stay perfectly phase-aligned without any JavaScript or timer synchronisation.

Customize

  • Add more words by inserting additional span children and extending the translateY keyframe steps — each word gets an equal time slice of 100% / N.
  • Swap the highlight style from a solid background to an underline by removing background and adding border-bottom: 3px solid on the container.
  • Adjust swap speed by changing the root animation duration — all child timings inherit from the parent loop variable via calc().

Watch out for

  • Words of very different lengths create jarring width jumps — keep your word list within a ±4 character range or use min-width to set a floor.
  • The vertical translateY approach will clip descenders on letters like g, p, y — add extra line-height or padding-bottom to the inner spans.
  • Safari occasionally drops the first frame of the loop, causing a flash of the wrong word — add a tiny animation-delay: 0.01s as a workaround.

Browser support

ChromeSafariFirefoxEdge
43+ 9+ 16+ 43+

Pure transform + width animation — no cutting-edge features required.

Search CodeFronts

Loading…