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.
Published
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;
}
}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 clip-path Reveal
Source: https://codefronts.com/motion/css-typewriter-effect/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.
## HTML
```html
<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>
```
## CSS
```css
.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;
}
}
```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 clip-path Reveal
Source: https://codefronts.com/motion/css-typewriter-effect/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.
## HTML
```html
<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>
```
## CSS
```css
.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.
Make it yours
- 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.
Gotchas — read before shipping
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.