CSS Typewriter steps() Cursor
The canonical CSS-only typewriter: width animates from 0 to a fixed ch value using steps() timing, paired with a blinking border-right cursor — no JS at all.
Published
The code
<div class="tw-01">
<div class="tw-01__stage">
<p class="tw-01__label">Frontend Developer</p>
<h1 class="tw-01__text">Hello, World.</h1>
<p class="tw-01__sub">Building beautiful interfaces with code.</p>
</div>
</div><div class="tw-01">
<div class="tw-01__stage">
<p class="tw-01__label">Frontend Developer</p>
<h1 class="tw-01__text">Hello, World.</h1>
<p class="tw-01__sub">Building beautiful interfaces with code.</p>
</div>
</div>.tw-01,
.tw-01 *,
.tw-01 *::before,
.tw-01 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tw-01 ::selection {
background: #a78bfa;
color: #0f0a1e;
}
.tw-01 {
--bg: #0f0a1e;
--surface: #1a1033;
--accent: #a78bfa;
--text: #e2d9f3;
--muted: #6b5fa0;
font-family: 'Courier New', Courier, monospace;
min-height: 340px;
display: flex;
align-items: center;
justify-content: center;
padding: 48px 24px;
}
.tw-01__stage {
display: flex;
flex-direction: column;
gap: 16px;
}
.tw-01__label {
font-size: 0.75rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent);
opacity: 0;
animation: tw-01-fadein 0.4s 0.2s forwards;
}
.tw-01__text {
font-size: clamp(1.8rem, 5vw, 3rem);
font-weight: 700;
color: var(--text);
overflow: hidden;
white-space: nowrap;
width: 0;
border-right: 3px solid var(--accent);
animation: tw-01-type 1.1s steps(13) 0.8s forwards, tw-01-blink 0.75s steps(2) 0.8s infinite;
}
.tw-01__sub {
font-size: 0.95rem;
color: var(--muted);
overflow: hidden;
white-space: nowrap;
width: 0;
animation: tw-01-sub 1.5s steps(38) 2.2s forwards;
}
@keyframes tw-01-type {
from {
width: 0;
}
to {
width: 13ch;
}
}
@keyframes tw-01-sub {
from {
width: 0;
}
to {
width: 38ch;
}
}
@keyframes tw-01-blink {
0%,
100% {
border-color: var(--accent);
}
50% {
border-color: transparent;
}
}
@keyframes tw-01-fadein {
to {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.tw-01__text {
width: 13ch;
animation: none;
border-right-color: var(--accent);
}
.tw-01__sub {
width: 38ch;
animation: none;
}
.tw-01__label {
opacity: 1;
animation: none;
}
}.tw-01,
.tw-01 *,
.tw-01 *::before,
.tw-01 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tw-01 ::selection {
background: #a78bfa;
color: #0f0a1e;
}
.tw-01 {
--bg: #0f0a1e;
--surface: #1a1033;
--accent: #a78bfa;
--text: #e2d9f3;
--muted: #6b5fa0;
font-family: 'Courier New', Courier, monospace;
min-height: 340px;
display: flex;
align-items: center;
justify-content: center;
padding: 48px 24px;
}
.tw-01__stage {
display: flex;
flex-direction: column;
gap: 16px;
}
.tw-01__label {
font-size: 0.75rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent);
opacity: 0;
animation: tw-01-fadein 0.4s 0.2s forwards;
}
.tw-01__text {
font-size: clamp(1.8rem, 5vw, 3rem);
font-weight: 700;
color: var(--text);
overflow: hidden;
white-space: nowrap;
width: 0;
border-right: 3px solid var(--accent);
animation: tw-01-type 1.1s steps(13) 0.8s forwards, tw-01-blink 0.75s steps(2) 0.8s infinite;
}
.tw-01__sub {
font-size: 0.95rem;
color: var(--muted);
overflow: hidden;
white-space: nowrap;
width: 0;
animation: tw-01-sub 1.5s steps(38) 2.2s forwards;
}
@keyframes tw-01-type {
from {
width: 0;
}
to {
width: 13ch;
}
}
@keyframes tw-01-sub {
from {
width: 0;
}
to {
width: 38ch;
}
}
@keyframes tw-01-blink {
0%,
100% {
border-color: var(--accent);
}
50% {
border-color: transparent;
}
}
@keyframes tw-01-fadein {
to {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.tw-01__text {
width: 13ch;
animation: none;
border-right-color: var(--accent);
}
.tw-01__sub {
width: 38ch;
animation: none;
}
.tw-01__label {
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 steps() Cursor
Source: https://codefronts.com/motion/css-typewriter-effect/css-typewriter-steps-cursor/
The canonical CSS-only typewriter: width animates from 0 to a fixed ch value using steps() timing, paired with a blinking border-right cursor — no JS at all.
## HTML
```html
<div class="tw-01">
<div class="tw-01__stage">
<p class="tw-01__label">Frontend Developer</p>
<h1 class="tw-01__text">Hello, World.</h1>
<p class="tw-01__sub">Building beautiful interfaces with code.</p>
</div>
</div>
```
## CSS
```css
.tw-01,
.tw-01 *,
.tw-01 *::before,
.tw-01 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tw-01 ::selection {
background: #a78bfa;
color: #0f0a1e;
}
.tw-01 {
--bg: #0f0a1e;
--surface: #1a1033;
--accent: #a78bfa;
--text: #e2d9f3;
--muted: #6b5fa0;
font-family: 'Courier New', Courier, monospace;
min-height: 340px;
display: flex;
align-items: center;
justify-content: center;
padding: 48px 24px;
}
.tw-01__stage {
display: flex;
flex-direction: column;
gap: 16px;
}
.tw-01__label {
font-size: 0.75rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent);
opacity: 0;
animation: tw-01-fadein 0.4s 0.2s forwards;
}
.tw-01__text {
font-size: clamp(1.8rem, 5vw, 3rem);
font-weight: 700;
color: var(--text);
overflow: hidden;
white-space: nowrap;
width: 0;
border-right: 3px solid var(--accent);
animation: tw-01-type 1.1s steps(13) 0.8s forwards, tw-01-blink 0.75s steps(2) 0.8s infinite;
}
.tw-01__sub {
font-size: 0.95rem;
color: var(--muted);
overflow: hidden;
white-space: nowrap;
width: 0;
animation: tw-01-sub 1.5s steps(38) 2.2s forwards;
}
@keyframes tw-01-type {
from {
width: 0;
}
to {
width: 13ch;
}
}
@keyframes tw-01-sub {
from {
width: 0;
}
to {
width: 38ch;
}
}
@keyframes tw-01-blink {
0%,
100% {
border-color: var(--accent);
}
50% {
border-color: transparent;
}
}
@keyframes tw-01-fadein {
to {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.tw-01__text {
width: 13ch;
animation: none;
border-right-color: var(--accent);
}
.tw-01__sub {
width: 38ch;
animation: none;
}
.tw-01__label {
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 steps() Cursor
Source: https://codefronts.com/motion/css-typewriter-effect/css-typewriter-steps-cursor/
The canonical CSS-only typewriter: width animates from 0 to a fixed ch value using steps() timing, paired with a blinking border-right cursor — no JS at all.
## HTML
```html
<div class="tw-01">
<div class="tw-01__stage">
<p class="tw-01__label">Frontend Developer</p>
<h1 class="tw-01__text">Hello, World.</h1>
<p class="tw-01__sub">Building beautiful interfaces with code.</p>
</div>
</div>
```
## CSS
```css
.tw-01,
.tw-01 *,
.tw-01 *::before,
.tw-01 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tw-01 ::selection {
background: #a78bfa;
color: #0f0a1e;
}
.tw-01 {
--bg: #0f0a1e;
--surface: #1a1033;
--accent: #a78bfa;
--text: #e2d9f3;
--muted: #6b5fa0;
font-family: 'Courier New', Courier, monospace;
min-height: 340px;
display: flex;
align-items: center;
justify-content: center;
padding: 48px 24px;
}
.tw-01__stage {
display: flex;
flex-direction: column;
gap: 16px;
}
.tw-01__label {
font-size: 0.75rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent);
opacity: 0;
animation: tw-01-fadein 0.4s 0.2s forwards;
}
.tw-01__text {
font-size: clamp(1.8rem, 5vw, 3rem);
font-weight: 700;
color: var(--text);
overflow: hidden;
white-space: nowrap;
width: 0;
border-right: 3px solid var(--accent);
animation: tw-01-type 1.1s steps(13) 0.8s forwards, tw-01-blink 0.75s steps(2) 0.8s infinite;
}
.tw-01__sub {
font-size: 0.95rem;
color: var(--muted);
overflow: hidden;
white-space: nowrap;
width: 0;
animation: tw-01-sub 1.5s steps(38) 2.2s forwards;
}
@keyframes tw-01-type {
from {
width: 0;
}
to {
width: 13ch;
}
}
@keyframes tw-01-sub {
from {
width: 0;
}
to {
width: 38ch;
}
}
@keyframes tw-01-blink {
0%,
100% {
border-color: var(--accent);
}
50% {
border-color: transparent;
}
}
@keyframes tw-01-fadein {
to {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.tw-01__text {
width: 13ch;
animation: none;
border-right-color: var(--accent);
}
.tw-01__sub {
width: 38ch;
animation: none;
}
.tw-01__label {
opacity: 1;
animation: none;
}
}
```How this works
The element has overflow: hidden; white-space: nowrap and animates from width: 0 to width: 22ch using steps(22) — each step snaps the width to reveal exactly one more character. Because steps() produces discrete jumps rather than a smooth ease, the text appears to be typed in real time without any scripting.
A second border-right: 3px solid animation toggles between the accent colour and transparent at steps(2), creating the hard on/off blink of a real terminal cursor. Both animations run on the same element; the cursor animation loops infinitely while the typing animation plays once with animation-fill-mode: forwards to hold the final state.
Make it yours
- Match
steps(N)andwidth: Nchto the exact character count of your string — one step per character for pixel-perfect snapping. - Change cursor style from a right-border to an underline by switching to
border-bottomand removingborder-right. - Add
animation-delay: 0.5sandanimation-fill-mode: bothto pause before the text starts appearing, useful for hero section entrances. - Use
font-variant-numeric: tabular-numson numeric strings to keep character widths uniform across all digits.
Gotchas — read before shipping
- Proportional fonts break the ch-based character count — this technique only works reliably with monospace fonts where every glyph is exactly 1ch wide.
- If you resize the container the cursor may drift; pin the element to
width: fit-content; max-width: Nchand avoid percentage widths on the parent. - The blinking cursor is invisible in some high-contrast Windows accessibility modes — add a
forced-colors: activefallback that makes the borderButtonText.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 4+ | 4+ | 3.5+ | 4+ |
Universally supported — the oldest reliable CSS animation technique.