Picture a hero paragraph that "dances in" letter by letter, three seconds of animation before anyone can read the first sentence. Most visitors look away before the text finishes moving. That's the trap with kinetic typography: it's genuinely powerful and genuinely easy to ruin.
Kinetic type isn't new, title sequences have used it since the 1950s, but it's finally practical on the web without a video embed or a GIF. Modern CSS handles most of the heavy lifting natively, and that changes what's worth building.
TL;DR: Kinetic typography works when it's brief, GPU-accelerated (transform and opacity only), and respects
prefers-reduced-motion. As many as 35% of adults over 40 have experienced some vestibular dysfunction (The A11Y Project), so motion isn't optional accessibility, it's a requirement.
What Actually Counts as Kinetic Typography?
Kinetic typography is text whose meaning is carried, or amplified, by its motion. Not "the button fades in." I mean a headline where weight, position, or scale changes to emphasize specific words, or a paragraph that reveals itself in sync with how far you've scrolled. Isn't the whole point of type to sit still so people can read it? Sure, most of the time. But for a hero moment, one line that moves with intent can do work that static text can't: it slows a visitor down for half a second longer, and that's often enough to land the message.
The failure mode is treating motion as decoration rather than emphasis. If you can delete the animation and the message reads exactly the same, the animation wasn't doing anything except burning battery and CPU cycles.
How Do You Make Kinetic Typography With CSS?
Three approaches cover almost every real kinetic typography use case:
Variable font axis interpolation. Animate font-variation-settings on the weight axis and you get text that visually "breathes" without changing layout at all, because the character widths barely shift.
.headline {
font-variation-settings: "wght" 400;
transition: font-variation-settings 0.6s ease;
}
.headline:hover, .headline.in-view {
font-variation-settings: "wght" 800;
}
Scroll-driven reveals with animation-timeline. No JavaScript required, and it ties the animation directly to scroll position instead of a fragile Intersection Observer callback. How the text eases along that scroll range is a topic of its own, covered in scroll-driven animation timing.
.reveal-text {
animation: fade-slide-up linear;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
@keyframes fade-slide-up {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
Staggered character reveal. Split text into spans (server-side or with a tiny script), then delay each span slightly using nth-child.
.stagger span { animation: rise 0.5s ease both; }
.stagger span:nth-child(2) { animation-delay: 0.05s; }
.stagger span:nth-child(3) { animation-delay: 0.10s; }
If deleting the animation doesn't change the message, the animation wasn't doing anything.
| Technique | What it does | JavaScript needed |
|---|---|---|
| Variable font axis interpolation | Text "breathes" via font-variation-settings, no layout shift | No |
| Scroll-driven reveals (animation-timeline) | Ties animation to scroll position | No |
| Staggered character reveal | Delays each character span slightly | Only to split text into spans |
What Are the Principles Behind Type That Moves Well?
Four principles decide whether animated text reads as craft or as noise: hierarchy, pacing, easing, and synchronization. Hierarchy says only one element in a view may be the loudest thing moving. Pacing sets how long a reader waits before the words hold still. Easing describes the shape of that movement in time, and synchronization ties it to something real, a scroll position, a beat, a cut.
Almost every kinetic type failure traces back to one of those four, usually hierarchy. Three animations firing at once is not a hierarchy, it's a fight.
| Principle | The question it answers | Fails when |
|---|---|---|
| Hierarchy | Which single element earns the motion? | Two or more things animate at the same moment |
| Pacing | How long until the text is readable? | Total reveal runs past ~800ms |
| Easing | What shape does the movement have? | Linear timing on anything a person will read |
| Synchronization | What is the motion tied to? | Motion fires on a timer unrelated to the reader |
Hierarchy: one loud thing per view
Pick the element that carries the message and let it move. Everything else in that viewport stays put. On a hero section that usually means the headline moves and the subhead, the button, and the eyebrow label do not, even though every one of them would look nice with a fade. Restraint is the design decision here, not the animation.
A useful rule, borrowed from title design: if a viewer can't say afterwards which word mattered, the motion did nothing. Does that sound severe? It saves arguments in review, because "which word matters" is a question a client can actually answer.
How Fast Should Animated Text Move?
Keep the whole reveal under 800 milliseconds, and keep any individual character stagger under 40ms per letter. Past that the reader is waiting on your animation instead of reading, and waiting is the one thing a headline can't afford.
The arithmetic gets brutal fast. A twelve-character word at a 60ms stagger takes 720ms just to finish appearing, before the per-letter animation itself even resolves. Add a 500ms fade on each letter and your three-word headline needs almost two seconds. At that length, visitors scroll past the hero before it settles.
Here's the timing I use as a starting point, adjusted per project rather than treated as law:
:root {
--type-motion-fast: 180ms; /* single word or short label */
--type-motion-base: 320ms; /* headline fade and rise */
--type-motion-slow: 520ms; /* scroll-scrubbed, reader controls it */
--type-stagger: 32ms; /* per character, never above 40ms */
}
Scroll-scrubbed motion is the exception, and it's the interesting one. When the animation timeline is the scroll position, the reader sets the speed themselves, so a longer range is fine. Nobody complains about a slow animation they're driving.
Which Easing Curve Should Text Use?
Use a decelerating curve for anything entering the screen, something close to cubic-bezier(0.22, 1, 0.36, 1), so the text arrives quickly and settles gently. Linear easing on type looks mechanical because nothing in the physical world starts and stops at a constant rate.
Overshoot curves, the springy ones that pass the target and bounce back, belong on playful brand work and almost nowhere else. A bouncing paragraph is genuinely harder to read, because the eye keeps re-fixating while the glyphs are still settling. My blunt opinion: if the text is longer than five words, skip the bounce entirely, and if someone insists, ask them to read the result out loud while it animates.
.headline {
transition: transform var(--type-motion-base) cubic-bezier(0.22, 1, 0.36, 1),
opacity var(--type-motion-base) linear;
}
Note the split. Opacity can be linear because a fade has no physical analogue, while position and scale need the deceleration. Mixing the two in a single shorthand is a small thing that separates motion that feels designed from motion that feels generated.
What Should the Motion Be Synchronized To?
Tie the animation to something the reader can perceive: their own scroll, a beat in an accompanying track, or a cut in a video behind the type. Motion that fires on an arbitrary timer feels disconnected, because it is.
Scroll is the most reliable sync source on the web, and animation-timeline: view() gives it to you natively now. For type over video or audio, sync to the cut rather than to the beat unless you've actually storyboarded to the track, since a headline landing half a frame off a hard cut reads as a bug. In broadcast work the frame is the unit and the tolerance is roughly one frame at 25fps, 40ms, which is also, not coincidentally, about the stagger ceiling above.
The web has no frame clock, so the closest equivalent is the scroll range. Define it explicitly with animation-range instead of letting the default do something you never chose.
How Do You Keep the Motion From Killing Performance?
Only animate transform and opacity. That's the whole rule, and I break it out on its own because it's the single most common mistake in kinetic type work. Every other CSS property, width, top, font-size, letter-spacing, forces the browser to recalculate layout on every frame. Transform and opacity get handled by the compositor thread, so the animation stays smooth even on a mid-range phone from three years ago. And when a type effect genuinely needs heavy work up front, treat it as a budget decision, not a sin: we drew those lines in blocking the main thread is fine.
There's a step before any of this, though. The font has to be on the page first, and a headline that animates in over a flash of invisible text reads as broken no matter how smooth the motion is. Get the loading strategy right before you touch a keyframe.
Test it on real hardware, not just your M-series laptop. A headline animation can run at a buttery 60fps in Chrome DevTools and then choke on an actual Android budget phone, because nobody tested outside the dev machine. If you can't test on a cheap Android device, throttle the CPU 4x in DevTools before you sign off on any motion work.
What About Accessibility and Motion Sensitivity?
This is where kinetic typography earns its bad reputation, and honestly, it's earned it. Wrap every animation in a prefers-reduced-motion check, full stop, no exceptions (web.dev):
@media (prefers-reduced-motion: reduce) {
.headline, .reveal-text, .stagger span {
animation: none !important;
transition: none !important;
}
}
That media query isn't a nice-to-have accessibility checkbox. Vestibular disorders affect a real chunk of your audience, and parallax-style scroll effects are one of the most commonly reported triggers. Stakeholders sometimes push back, insisting the reduced-motion fallback "looks boring." It should look boring. Boring and readable beats dizzying and abandoned every time you're measuring actual conversions instead of Awwwards votes.
Want to see the sizing math behind these effects before you build one? Our free type scale calculator generates a modular scale with copy-ready CSS custom properties, useful groundwork before you start animating weight and size together.
Where Can You See This Done Right?
Reference examples matter more than any code snippet here. We keep a running collection of motion-plus-type work in our styleframes gallery, pulled from real production sites and our own experiments, specifically so you can see how weight, scroll-timing, and restraint work together instead of imagining it from a code sample. If you're deciding between a CSS-only approach and something like GSAP for more elaborate choreography, our CSS versus GSAP breakdown walks through exactly where the tradeoff sits, and it's worth reading before you commit a client budget to either path.
Kinetic type isn't a trend you can skip forever, but it's also not something every headline needs. Pick one moment. Make it earn its motion.
Work out the stagger
A staggered character reveal is a delay on every span, and the delays have to fit inside a budget or the headline lands after the reader has gone. Put in your character count, how long one letter takes and the moment the last must land, then read the step between neighbours back.