14 CSS Typewriter Effect Designs 05 / 14
CSS Typewriter clip-path Reveal
Text is revealed character-by-character using an animated clip-path inset that travels left-to-right — giving a hard-edged paint-stroke reveal instead of the usual overflow trick.
The code
<div class="tw-05">
<div class="tw-05__stage">
<div class="tw-05__eyebrow">Award-winning studio</div>
<h1 class="tw-05__heading">
<span class="tw-05__line tw-05__line--1">Craft that</span>
<span class="tw-05__line tw-05__line--2">moves people.</span>
</h1>
<div class="tw-05__cursor"></div>
</div>
</div> <div class="tw-05">
<div class="tw-05__stage">
<div class="tw-05__eyebrow">Award-winning studio</div>
<h1 class="tw-05__heading">
<span class="tw-05__line tw-05__line--1">Craft that</span>
<span class="tw-05__line tw-05__line--2">moves people.</span>
</h1>
<div class="tw-05__cursor"></div>
</div>
</div>.tw-05, .tw-05 *, .tw-05 *::before, .tw-05 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.tw-05 ::selection { background: #ec4899; color: #1a0010; }
.tw-05 {
--pink: #ec4899;
--rose: #fb7185;
--bg: #0d0008;
--text: #fdf2f8;
--muted: #831843;
font-family: 'Georgia', 'Times New Roman', serif;
min-height: 340px;
display: flex;
align-items: center;
justify-content: center;
padding: 48px 32px;
}
.tw-05__stage {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 480px;
}
.tw-05__eyebrow {
font-size: 0.72rem;
letter-spacing: 0.25em;
text-transform: uppercase;
color: var(--pink);
clip-path: inset(0 100% 0 0);
animation: tw-05-reveal 0.6s steps(1) 0.3s forwards;
}
.tw-05__heading {
display: flex;
flex-direction: column;
gap: 4px;
}
.tw-05__line {
display: block;
position: relative;
font-size: clamp(2rem, 6vw, 3.8rem);
font-weight: 700;
color: var(--text);
line-height: 1.1;
clip-path: inset(0 100% 0 0);
overflow: hidden;
}
.tw-05__line::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, var(--pink), var(--rose));
opacity: 0.25;
clip-path: inset(0 100% 0 0);
}
.tw-05__line--1 {
animation: tw-05-reveal 0.9s steps(10) 0.8s forwards;
}
.tw-05__line--1::before {
animation: tw-05-reveal 0.9s steps(10) 0.6s forwards;
}
.tw-05__line--2 {
animation: tw-05-reveal 1.1s steps(13) 2s forwards;
}
.tw-05__line--2::before {
animation: tw-05-reveal 1.1s steps(13) 1.8s forwards;
}
.tw-05__cursor {
width: 3px;
height: 2.4rem;
background: var(--pink);
box-shadow: 0 0 12px var(--pink);
animation: tw-05-blink 0.8s steps(2) 2.8s infinite;
opacity: 0;
}
@keyframes tw-05-reveal {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0% 0 0); }
}
@keyframes tw-05-blink {
0%,100% { opacity: 1; }
50% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.tw-05__eyebrow, .tw-05__line, .tw-05__line::before { clip-path: none; animation: none; }
.tw-05__cursor { opacity: 1; animation: none; }
} .tw-05, .tw-05 *, .tw-05 *::before, .tw-05 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.tw-05 ::selection { background: #ec4899; color: #1a0010; }
.tw-05 {
--pink: #ec4899;
--rose: #fb7185;
--bg: #0d0008;
--text: #fdf2f8;
--muted: #831843;
font-family: 'Georgia', 'Times New Roman', serif;
min-height: 340px;
display: flex;
align-items: center;
justify-content: center;
padding: 48px 32px;
}
.tw-05__stage {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 480px;
}
.tw-05__eyebrow {
font-size: 0.72rem;
letter-spacing: 0.25em;
text-transform: uppercase;
color: var(--pink);
clip-path: inset(0 100% 0 0);
animation: tw-05-reveal 0.6s steps(1) 0.3s forwards;
}
.tw-05__heading {
display: flex;
flex-direction: column;
gap: 4px;
}
.tw-05__line {
display: block;
position: relative;
font-size: clamp(2rem, 6vw, 3.8rem);
font-weight: 700;
color: var(--text);
line-height: 1.1;
clip-path: inset(0 100% 0 0);
overflow: hidden;
}
.tw-05__line::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, var(--pink), var(--rose));
opacity: 0.25;
clip-path: inset(0 100% 0 0);
}
.tw-05__line--1 {
animation: tw-05-reveal 0.9s steps(10) 0.8s forwards;
}
.tw-05__line--1::before {
animation: tw-05-reveal 0.9s steps(10) 0.6s forwards;
}
.tw-05__line--2 {
animation: tw-05-reveal 1.1s steps(13) 2s forwards;
}
.tw-05__line--2::before {
animation: tw-05-reveal 1.1s steps(13) 1.8s forwards;
}
.tw-05__cursor {
width: 3px;
height: 2.4rem;
background: var(--pink);
box-shadow: 0 0 12px var(--pink);
animation: tw-05-blink 0.8s steps(2) 2.8s infinite;
opacity: 0;
}
@keyframes tw-05-reveal {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0% 0 0); }
}
@keyframes tw-05-blink {
0%,100% { opacity: 1; }
50% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.tw-05__eyebrow, .tw-05__line, .tw-05__line::before { clip-path: none; animation: none; }
.tw-05__cursor { opacity: 1; animation: none; }
}How this works
Instead of animating width, the text is full-width from the start but masked by clip-path: inset(0 100% 0 0). Animating the second value from 100% to 0% with steps(N) timing peels the mask away from left to right, one character-width at a time. This approach works with proportional fonts and variable-width containers — no ch counting required.
A separate highlight layer, an absolutely-positioned ::before pseudo-element with the same clip-path animation but a slight delay, creates a sweeping colour wash effect that advances just ahead of the revealed text — mimicking a highlighter pen uncovering the words. The two animations share the same duration and step count so they stay locked in sync.
Customize
- Change the reveal direction to right-to-left by animating
clip-path: inset(0 0 0 100%)toinset(0 0 0 0). - Add a vertical reveal (top-to-bottom) by using
inset(0 0 100% 0)toinset(0 0 0 0)for dramatic line-by-line reveals on large headings. - Combine with
animation-delaystagger on multiple lines to create a staggered paint-stroke entrance for a whole paragraph.
Watch out for
clip-pathanimations withsteps()may produce visible aliasing on sub-pixel boundaries — usewill-change: clip-pathand ensure GPU compositing is active.- The cursor element needs to be positioned absolutely and move in sync with the clip reveal; it cannot rely on normal text flow once clip-path is applied.
- Safari before 15.4 has inconsistent
clip-path: inset()animation interpolation — test carefully and provide awidth-based fallback.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 55+ | 13.1+ | 54+ | 55+ |
clip-path animation on inset() requires Safari 13.1+; use @supports for progressive enhancement.