- Published on
Mastering the CSS Typing Animation: A Step-by-Step Guide
- Authors
- Name
- Md Nasim Sheikh
- @nasimStg
'Mastering the CSS Typing Animation: A Step-by-Step Guide'
Learn how to create a captivating typing animation from scratch using only CSS. This comprehensive guide covers everything from single-line effects and blinking cursors to advanced multi-line and delete animations.
Table of Contents
- 'Mastering the CSS Typing Animation: A Step-by-Step Guide'
- The Magic Behind the Curtain: Core Concepts
- Section 1: Your First Typing Animation (Single Line)
- The HTML Structure
- The CSS Breakdown
- The Complete Code
- Section 2: Going Further - Pause, Delete, and Retype
- The Concept: Multi-Step Keyframes
- The CSS Implementation
- Section 3: Handling Multiple Lines
- Multi-Line with Fixed Characters
- Section 4: Best Practices and Final Polish
- 1. Respect User Preferences with prefers-reduced-motion
- 2. The Importance of monospace Fonts
- 3. SEO and Performance
- Conclusion
Have you ever landed on a website and been greeted by a cool, dynamic typing effect in the hero section? It's a small touch that can make a big impact, adding a sense of interactivity and polish. You might think such an effect requires complex JavaScript, but you'd be surprised what you can achieve with pure CSS.
In this comprehensive guide, we'll dive deep into the world of CSS animations to build a variety of typing effects from the ground up. Whether you're a CSS novice or a seasoned pro, you'll walk away with the skills to create engaging, performant, and accessible typing animations for your own projects.
We'll cover:
- The core CSS properties that make it all possible.
- Building a classic single-line typing effect with a blinking cursor.
- Handling multiple lines of text.
- Creating advanced effects like pausing and deleting text.
- Best practices for accessibility and performance.
Ready? Let's get typing!
The Magic Behind the Curtain: Core Concepts
Before we write a single line of code, it's crucial to understand the illusion we're creating. We aren't actually simulating a user typing character by character. Instead, we are taking a complete line of text and gradually revealing it. The trick lies in making this reveal look like typing.
Two key CSS features make this possible:
@keyframes
Animations: This is the heart of any CSS animation. Keyframes allow us to define the stages and styles of an animation sequence. We'll define a starting state (text is hidden) and an ending state (text is fully visible).The
steps()
Timing Function: This is our secret weapon. Most CSS animations are smooth, transitioning from one state to another over a duration (thinkease-in-out
orlinear
). Thesteps()
function is different. It breaks an animation into a specified number of discrete intervals. Instead of a smooth transition, the animation jumps from step to step. This is perfect for revealing text one character at a time, like a flipbook.
Imagine you have the text "Hello World" (11 characters). We can tell our animation to complete in 11 steps. In each step, a little more of the text is revealed, perfectly simulating the act of typing.
Section 1: Your First Typing Animation (Single Line)
Let's start with the most common use case: a single line of text that types itself out, complete with a blinking cursor.
The HTML Structure
The HTML is as simple as it gets. We just need an element to hold our text. A div
or a p
tag with a class is perfect.
<div class="container">
<div class="typing-effect">Hello World! This is a CSS typing animation.</div>
</div>
The CSS Breakdown
Now for the fun part. We'll build up the CSS step-by-step.
Step 1: The Basic Setup
First, we'll create our typing animation using @keyframes
. We'll name it typing
. This animation will animate the width
of our element from 0
(completely hidden) to 100%
(fully visible).
@keyframes typing {
from { width: 0 }
to { width: 100% }
}
Simple, right? The width
is the property we'll be manipulating to reveal the text.
Step 2: Styling the Text Element
Now, let's apply this animation to our .typing-effect
class. There are a few crucial properties we need to add here:
overflow: hidden;
: This is essential. As we animate the width from 0 to 100%, we need to hide the part of the text that is outside the current width. Without this, the full text would be visible from the start.white-space: nowrap;
: This prevents the text from wrapping to a new line as the container's width expands. If we didn't have this, the text would reflow and ruin the illusion.animation
: This is where we apply ourtyping
keyframes and, most importantly, thesteps()
function.
Let's look at the code:
.typing-effect {
/* The number of characters in the text */
--char-count: 42;
width: calc(var(--char-count) * 1ch);
overflow: hidden; /* Hide the text that is not yet "typed" */
white-space: nowrap; /* Prevent text from wrapping */
border-right: 3px solid orange; /* The blinking cursor */
font-family: monospace;
font-size: 2rem;
animation:
typing 3s steps(var(--char-count), end),
blink-caret .75s step-end infinite;
}
Let's break down that animation
property, as it's doing a lot:
typing 3s steps(var(--char-count), end)
:typing
: The name of our@keyframes
animation.3s
: The duration. The entire text will be typed out in 3 seconds.steps(var(--char-count), end)
: This is the magic. We're telling the animation to take 42 steps (the number of characters in our text). Theend
keyword means the jump to the next step happens at the end of each interval.
blink-caret .75s step-end infinite
: We're applying a second animation to the same element! This one will control the cursor. We'll define it next.
Wait, what's 1ch
and --char-count
?
- The
ch
unit in CSS is a typographic unit that represents the width of the '0' (zero) character in the element's font. Inmonospace
fonts, where every character has the same width,1ch
is effectively the width of one character. This is incredibly useful! - By setting
width: calc(var(--char-count) * 1ch)
, we are making the element's final width exactly equal to the width of its text content. This is a more robust method than animatingwidth
to100%
, especially for more advanced effects. --char-count
is a CSS Custom Property (or variable) that holds the number of characters. This makes our code much more maintainable. If you change the text, you only need to update this one variable.
Step 3: Creating the Blinking Cursor
A typing animation isn't complete without the iconic blinking cursor. We've already added a border-right
to our element to serve as the cursor. Now, we just need to make it blink.
We'll create another @keyframes
animation for this.
@keyframes blink-caret {
from, to { border-color: transparent }
50% { border-color: orange; }
}
This animation simply changes the border-color
from transparent to orange and back again, creating a blinking effect. In our main class, we applied it with blink-caret .75s step-end infinite
. The step-end
timing function makes the color change instantly, giving us a crisp blink rather than a slow fade.
The Complete Code
Here is the full, working code for our single-line typing animation. You can drop this into a Codepen and see it in action!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Typing Effect</title>
<style>
body {
display: grid;
place-items: center;
height: 100vh;
background: #222;
color: #eee;
}
.typing-effect {
/* The number of characters in the text */
--char-count: 42;
width: calc(var(--char-count) * 1ch);
overflow: hidden;
white-space: nowrap;
border-right: 3px solid orange;
font-family: monospace;
font-size: 2rem;
animation:
typing 3s steps(var(--char-count), end),
blink-caret .75s step-end infinite;
}
/* The typing animation */
@keyframes typing {
from { width: 0 }
to { width: calc(var(--char-count) * 1ch) }
}
/* The cursor blinking animation */
@keyframes blink-caret {
from, to { border-color: transparent }
50% { border-color: orange; }
}
</style>
</head>
<body>
<div class="typing-effect">Hello World! This is a CSS typing animation.</div>
</body>
</html>
Section 2: Going Further - Pause, Delete, and Retype
What if you want to simulate a more realistic typing pattern? For instance, typing a word, pausing, deleting it, and typing something new. This is often used to cycle through a list of adjectives (e.g., "We build Fast / Secure / Beautiful websites").
This requires a more complex @keyframes
definition, but the principle is the same.
The Concept: Multi-Step Keyframes
Instead of just from
and to
(which are aliases for 0%
and 100%
), we can define intermediate steps in our animation. We can tell the animation to reach a certain width, hold it there for a while, and then shrink it back down.
Let's create an animation that types "Developer.", pauses, deletes it, and types "Designer." instead.
The CSS Implementation
For this to work, we'll have two separate text elements and we'll apply different animations to them, timed to run one after the other.
Wait, that sounds like we need JavaScript for timing, right? Not necessarily! We can use animation-delay
.
Let's rethink. A pure CSS solution for changing text is tricky. The classic way to handle this is to have a single animation that types, pauses, and deletes. The changing of the text is usually handled by layering multiple elements or, more commonly, with a little JavaScript help.
However, we can create the illusion of changing text by creating a single, longer animation cycle on one line. Let's create an animation that types out a phrase, pauses, and then deletes it.
/* Let's assume our text is "Creative Developer." (19 chars) */
.typing-delete-effect {
--char-count: 19;
width: calc(var(--char-count) * 1ch);
overflow: hidden;
white-space: nowrap;
border-right: 3px solid #ccc;
font-family: monospace;
font-size: 2rem;
animation:
typing-and-deleting 6s steps(var(--char-count), end) infinite,
blink-caret .75s step-end infinite;
}
@keyframes typing-and-deleting {
/* Type out the text */
0%, 45% {
width: calc(var(--char-count) * 1ch);
}
/* Pause for a bit */
50%, 95% {
width: 0;
}
/* Delete the text */
100% {
width: 0;
}
}
/* We can reuse the same blink-caret animation */
@keyframes blink-caret {
from, to { border-color: transparent }
50% { border-color: #ccc; }
}
Let's analyze the new typing-and-deleting
keyframes:
0%, 45%
: The animation will spend the first 45% of its 6-second duration expanding the width to its full size. This is the "typing" phase.50%, 95%
: For the next large chunk of the animation, the width is held at0
. This creates the illusion of deleting the text and waiting before typing again.
This example is a bit of a hack. A more robust way to handle complex sequences of typing different words involves a bit of JavaScript to change the text content and CSS variables at the end of each animation cycle. But for a pure CSS effect that types and deletes the same text, this keyframe manipulation works wonders.
Section 3: Handling Multiple Lines
The width
animation technique is fantastic for single lines, but it falls apart for multiple lines of text. If you try it on a paragraph, the entire block will be revealed at once as the width expands, not line-by-line or character-by-character.
So, how do we solve this? The truth is, a character-by-character multi-line typing animation is extremely difficult with pure CSS in a way that is robust and maintainable. The number of characters per line can change based on screen width, making a steps()
count unreliable.
Most multi-line "typing" effects you see are actually line-by-line reveals, which are much simpler to achieve.
However, if we have a fixed-width container and know exactly how many characters are in our text block, we can still use our character-count method.
Let's try it.
Multi-Line with Fixed Characters
HTML:
<p class="multi-line-effect">
This is a multi-line typing animation created with pure CSS. It works by counting all characters, including spaces, and using the steps() function to reveal them one by one. It requires a fixed width and a monospace font to work reliably.
</p>
CSS:
.multi-line-effect {
/* Total characters = 227 */
--char-count: 227;
--line-height: 1.4;
/* Set a fixed width */
width: 65ch;
height: calc(5 * 1em * var(--line-height)); /* 5 lines of text */
overflow: hidden;
font-family: monospace;
font-size: 1.2rem;
line-height: var(--line-height);
/* We animate height instead of width */
animation:
typing-multi-line 8s steps(var(--char-count), end);
}
/* We animate the height from 0 to its full height */
@keyframes typing-multi-line {
from { height: 0 }
to { height: calc(5 * 1em * var(--line-height)) }
}
Wait, this isn't a character-by-character reveal!
You're right. The code above demonstrates a common misconception. Animating height
with steps
doesn't work the way animating width
does. It will reveal the content in chunky, blocky steps, not character by character.
The Real (But Complex) CSS Solution
To achieve a true multi-line, character-by-character effect, you would need to wrap every single line in its own <span>
and animate them sequentially using animation-delay
. This is brittle and a maintenance nightmare.
This is one of the few areas where a small amount of JavaScript to split the text into spans and apply animations is overwhelmingly the better, more robust solution. For the purposes of this pure CSS guide, it's important to acknowledge the limitations. The single-line effect is where CSS truly shines on its own.
Section 4: Best Practices and Final Polish
Creating a cool animation is one thing; making it professional, accessible, and performant is another. Here are some crucial best practices.
prefers-reduced-motion
1. Respect User Preferences with Some users experience motion sickness, vertigo, or distractions from web animations. As responsible developers, we must respect their preferences. CSS provides a simple media query for this.
Wrap your animation code in a @media
block to ensure it only runs for users who have not requested reduced motion.
@media (prefers-reduced-motion: no-preference) {
.typing-effect {
animation:
typing 3s steps(var(--char-count), end),
blink-caret .75s step-end infinite;
}
@keyframes typing {
from { width: 0 }
to { width: calc(var(--char-count) * 1ch) }
}
@keyframes blink-caret {
from, to { border-color: transparent }
50% { border-color: orange; }
}
}
This way, users who prefer reduced motion will simply see the final text instantly, without any animation. It's a critical accessibility win.
monospace
Fonts
2. The Importance of Throughout this guide, I've stressed the use of font-family: monospace;
. This is not just a stylistic choice.
- Proportional Fonts (like Arial, Times New Roman): The width of each character is different. An 'i' is much narrower than a 'W'. If you animate the
width
of a container holding a proportional font, the reveal will look jittery and uneven as each character takes up a different amount of space. - Monospace Fonts (like Courier, Fira Code, Source Code Pro): Every character has the exact same width. This ensures that as we animate the
width
in evensteps
, each step reveals exactly one character, resulting in a smooth, consistent, and believable typing effect.
3. SEO and Performance
SEO: You might worry that hiding the text initially will impact your site's SEO. Fear not! Search engine crawlers read the raw HTML source code. Your full text is present in the HTML from the moment the page loads. The animation is a purely visual layer for the user, so there is no negative SEO impact.
Performance: CSS animations that animate
transform
andopacity
are the most performant. While we are animatingwidth
, which can cause some browser repaints, the effect is generally very lightweight and performant for the small-scale use cases we've discussed. It's far more performant than a JavaScript-based solution that manipulates the DOM on every frame.
Conclusion
You've done it! You've gone from the basic principles of CSS animation to building a polished, responsive, and accessible typing effect.
We've learned that the magic lies in combining a few key CSS properties:
@keyframes
to define our animation sequence.- The
steps()
timing function to create a character-by-character reveal instead of a smooth one. - Animating the
width
of the element from0
to its full size. - Using
overflow: hidden
andwhite-space: nowrap
to control the text flow. - Leveraging the
ch
unit andmonospace
fonts for precision. - Applying a second animation for a blinking cursor.
While pure CSS has its limits with complex multi-line scenarios, it is a powerful and elegant solution for the vast majority of typing effects you see on the web. It's performant, great for SEO, and with the prefers-reduced-motion
media query, it's accessible too.
Now go ahead and add some dynamic flair to your next project!