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.
Published
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><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;
}
}.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;
}
}Here's a working CSS Typewriter Effect Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Typewriter Word Swap Loop
Source: https://codefronts.com/motion/css-typewriter-effect/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.
## HTML
```html
<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>
```
## CSS
```css
.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;
}
}
```Here's a working CSS Typewriter Effect Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Typewriter Word Swap Loop
Source: https://codefronts.com/motion/css-typewriter-effect/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.
## HTML
```html
<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>
```
## CSS
```css
.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.
Make it yours
- Add more words by inserting additional
spanchildren and extending thetranslateYkeyframe steps — each word gets an equal time slice of100% / N. - Swap the highlight style from a solid background to an underline by removing
backgroundand addingborder-bottom: 3px solidon the container. - Adjust swap speed by changing the root animation duration — all child timings inherit from the parent loop variable via
calc().
Gotchas — read before shipping
- Words of very different lengths create jarring width jumps — keep your word list within a ±4 character range or use
min-widthto set a floor. - The vertical translateY approach will clip descenders on letters like g, p, y — add extra
line-heightorpadding-bottomto 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.01sas a workaround.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 43+ | 9+ | 16+ | 43+ |
Pure transform + width animation — no cutting-edge features required.