When text animation goes wrong, the splitting step is usually to blame, not the motion. Adrian Roselli’s February 2026 tests of GSAP’s own SplitText demo found the text announced correctly in only 2 of 8 screen reader and browser pairings.

JavaScript text animation means a script controls the timing of text as it moves, appears or changes. It cuts the text into spans so each character, word or line can run on its own schedule, and a stagger, a timeline or a typing loop drives them. Plain CSS handles that kind of per-letter sequencing poorly.

You see it mostly in headlines and hero sections, and now and then in search placeholders. The usual effects are typewriter, staggered reveal, scramble and number counters.

What is JavaScript text animation and when does CSS fall short?

CSS keyframes cover a single element that fades, slides or pulses, a fixed list of words rotating on a loop, or any effect that starts on load and never reacts to anything. No script needed for those.

Per-letter sequencing is where it stops being practical. Every unit needs its own delay, and somebody has to write all those values.

JavaScript takes over when the text itself has to change and not only its style. Typing and scrambling both work that way. So does any effect where each character gets a computed delay, where the start depends on scroll position, hover or a runtime value, or where a number counts up to a figure read from data.

Mixed setups are common and often the best option. Script splits the text and writes an index onto each span, then a CSS rule reads that index to set the delay and does the actual animating.

Which text animation effects can you build?

See the Pen
Typography Animation
by Georgi Nikolov (@gbnikolov)
on CodePen.

Typewriter, fade-in, staggered reveal, scramble, rotating words and number counters cover most real projects. Each suits a different kind of text, and most of them fall apart on anything longer than a line or two.

Typing and rotating effects

See the Pen
Simple Typing Carousel
by SS (@CheeseTurtle)
on CodePen.

Algolia’s InstantSearch documentation recommends a random pause of 50 to 90 milliseconds between typed characters, because that range looks the most natural.

The same guide uses typing in animated search placeholders, where the motion draws attention to the search box and suggests possible queries.

What is shaping UX design today?

Uncover the newest UX design statistics: user behavior, design trends, ROI data, and insights driving better digital experiences.

Check Them Out →

A typewriter effect shows characters one at a time, usually with a blinking cursor and sometimes a delete phase. Rotating words are simpler. One word in a headline swaps for the next on a timer.

Reserve a fixed width for the rotating slot, or the rest of the line jumps on every swap. Both effects belong on one short line.

Reveal and stagger effects

See the Pen
Splitting Splash 🌊 | Bouncy CSS Text Animation with Splitting.js | @keyframers 3.1
by @keyframers (@keyframers)
on CodePen.

The same set of character spans can take a fade, a slide, a blur or a rotation. The stagger decides how it reads.

Stagger means each unit starts a fixed delay after the previous one. The order is a design choice, so it can run forward, backward, from the center outward, or at random.

Opacity from 0 to 1 plus a short translateY does most of the work, and a scale-up from slightly smaller works too. Stick to transform and opacity for reveals. Simple versions also exist in pure CSS, like this collection of CSS text animations, which is the better pick when the text never changes.

Scramble and counter effects

See the Pen
Decode Text Effect
by Ben Racicot (@BRacicot)
on CodePen.

Scramble effects flicker random characters, then settle on the final text. The shuffle-text library produces exactly this, and ICS.media’s roundup of text libraries points to hero sections and menus as its best fit.

Counters roll a number from its old value to a new one. NumberFlow, from the same roundup, handles smooth transitions between numeric values.

That roundup also carries a warning worth keeping. Animation should never make text harder to read or briefly cover important content.

How do you split text into characters, words and lines?

See the Pen
Responsive meandering text w/ Splitting 😎
by Jhey (@jh3y)
on CodePen.

Wrap each character, word or line in its own span, set display to inline-block so transforms apply, then animate the spans instead of the text. A splitting tool does the wrapping and can restore the original markup afterward.

Wrapping characters and words in spans

A browser treats raw text as a text node, which cannot be styled or animated on its own. Each unit has to sit inside an element first.

Letter spans need display: inline-block, because transforms do not apply to plain inline boxes. A wrapper around each word with white-space: nowrap stops a line from breaking in the middle of a word. Spaces stay outside the letter spans, so the paragraph still wraps normally.

Splitting.js and GSAP’s SplitText do this wrapping for you, and Anime.js v4 ships a splitText utility that returns characters, words and lines.

GSAP’s 3.13 rewrite of SplitText improved emoji and foreign character support and added a standalone mode that loads without GSAP’s core (GSAP, 2025).

Grapheme-safe splitting with Intl.Segmenter

Intl.Segmenter has been Baseline newly available since April 2024, according to MDN.

Chrome 87 and Edge 87 shipped it in 2020 and Safari 14.1 in 2021, per web.dev. Baseline waited for Firefox 125 in April 2024.

As of October 2026, the web-features data projects Widely available status for October 16, 2026.

The reason to care is simple. Splitting a string with split(“”) or the spread operator cuts it by code unit or code point, so an emoji sequence joined by zero-width joiners, or a letter with a combining accent, falls into pieces and each piece gets its own span. The animation then tears the symbol apart.

Grapheme granularity returns what a reader sees as one character. Word granularity is required for Chinese and Japanese, which do not separate words with spaces.

Splitting lines on resize

Line splitting measures where words wrap, so the result is only valid at the current width. A rotated phone or a late-loading font changes the breaks.

Run the split after document.fonts.ready resolves, or the measurement uses fallback metrics. To handle resizing, watch the container with a ResizeObserver, debounce the callback, restore the original markup, then split again. SplitText offers a revert() method for the restore step.

Skip this and lines end up broken in the wrong places after the first orientation change.

Which implementation approach fits the effect?

Match the approach to what changes. Styles on split text suit CSS plus a few lines of JavaScript, and sequenced reveals without a dependency suit the Web Animations API.

Text that itself changes (typing, scramble, counters) needs a script loop. Long timelines or scroll sync justify a library.

CSS plus JavaScript is the lightest route. Transform and opacity animate smoothly, the delay comes from a custom property set on each span, and the code stays tiny. The catch is that the text cannot change, and chaining steps means listening for animationend events.

The Web Animations API is native and needs no dependency. Element.animate() has worked across browsers since March 2020 and is Baseline Widely available (MDN), and each call returns an Animation object with playback controls and a finished promise. It animates CSS properties only, according to the Motion documentation, so typing and counters are out.

A timer or requestAnimationFrame loop gives you full control and is the only route for typing, scramble and counters. Easing, pausing and cleanup are yours to write, though.

Libraries bring timelines, stagger, scroll triggers and splitting built in. You pay in bundle weight, and when a few lines of CSS could do the job, it’s a dependency you didn’t need.

For most text effects the first two approaches are enough. Reach for a library when the timeline gets long, not before.

How do GSAP, Anime.js, Typed.js and Motion compare for text?

See the Pen
Greensock Text Animation
by Joseph Banegas (@jbanegas)
on CodePen.

GSAP has the broadest text toolset, Motion is the lightest for simple reveals, and Anime.js splits and staggers with a small API. Typed.js does one thing, typing.

LibraryText featuresSize and costBest fit
GSAPSplitText, TextPlugin, ScrollTriggerAbout 23.5 kB core, free since 2025Timelines and scroll-synced headlines
Anime.jssplitText, staggerModular and tree-shakeable in v4Stagger-heavy reveals
Typed.jsTyping, backspacing, looping stringsTyping onlyOne typed headline
Motionanimate(), stagger; split text in Motion+2.6 kB mini, 18 kB hybrid; core is MITLight reveals, React projects

Cost changed in 2025. After Webflow’s acquisition, GSAP became free for everyone, commercial use included, along with all former Club plugins such as SplitText (Webflow and GSAP announcements, 2025).

Motion’s own feature table puts split text under Motion+, its one-time-fee membership, while the core library is MIT licensed (npm).

The size figures come from Motion’s GSAP comparison page, which lists GSAP core at about 23.5 kB without tree-shaking. Motion’s docs disagree with themselves slightly, 2.6 kB for the mini build on the comparison page and 2.3 kB for the React useAnimate mini on the bundle-size page (the hybrid build is 18 kB on one and 17 kB on the other), so measure your own build output.

For a single typewriter line, I’d take Typed.js or twenty lines of vanilla JavaScript over loading a full animation library.

If you want class-based effects with no animation code at all, a ready-made CSS animations library such as Animate.css pairs well with a splitter like Splitting.js.

How to build a typewriter effect in vanilla JavaScript

See the Pen
Typing effect using JavaScript – Day 10 of #30Days30Projects
by Ishaan Sheikh (@sheikh_ishaan)
on CodePen.

Keep the full text in the DOM, reveal it character by character from a timed loop, and cancel the loop on teardown. The steps below get it working.

  1. Write the final text in the markup, then split it into character spans with Intl.Segmenter so emoji and accented letters stay whole.
  2. Set every span to opacity 0. The text keeps its final size, so the paragraph never reflows while it types.
  3. Start a requestAnimationFrame loop. On each frame, work out how many characters should be visible from the elapsed time, then set those spans to opacity 1.
  4. Mark the newest visible span with a caret (a CSS border or pseudo-element) and let it blink once the loop finishes.
  5. Cancel the frame request when the element leaves the DOM, and show the full text at once when the user prefers reduced motion.

Computing the count from elapsed time matters. requestAnimationFrame callbacks pause in background tabs, so a loop that adds one character per tick stalls, while an elapsed-time loop catches up the moment the tab returns.

A human rhythm needs a precomputed schedule of reveal times, because a per-tick random delay does not fit an elapsed-time loop.

Appending text with textContent also works, but every new character reflows the line and pushes anything below it. The DOM also holds only part of the sentence until the loop ends.

How do you trigger text animation on scroll, hover or page load?

See the Pen
Mark text effect on scroll w/ IO
by Mattia Astorino (@equinusocio)
on CodePen.

Start the animation from the event that matches where the text sits. Load events suit headlines already in view, IntersectionObserver suits text further down, and mouse or focus events suit interactive labels.

For page load, start on DOMContentLoaded, because the window load event waits for every image to finish first. For viewport entry, an IntersectionObserver callback starts the effect, then calls unobserve on the element so it runs once.

Scroll-linked animation is GSAP ScrollTrigger’s territory. It ties progress to scroll distance through its scrub option, with start and end positions you set. Hover and focus are the easy ones: mouseenter and focus start the effect, mouseleave and blur reset it.

Text above the fold should animate on load. Waiting for a scroll event leaves it static on the first screen.

IntersectionObserver has worked in every major browser since March 2019 and is Baseline Widely available, according to MDN, so it needs no polyfill.

The threshold option takes a ratio from 0 to 1 for how much of the element must be visible, and rootMargin grows or shrinks the root box on each side. A threshold of 0.2 plus a negative bottom margin delays the start until the text sits clearly inside the viewport.

Text inside a collapsed accordion or hidden tab has no box to intersect, so the observer never fires. Touch screens have no hover, so a hover-only effect needs a tap or viewport-entry fallback.

And hide the initial state with a class that script adds, not in plain CSS. Otherwise visitors with JavaScript off see an empty heading.

How do you keep text animation fast and free of layout shift?

Frame time decides it. Google’s web.dev puts the budget at about 16.7 ms per frame for 60 fps, and users notice the delay once frames run longer.

Split text multiplies the work, because every character span is its own animated box.

Transform and opacity changes can run on the compositor thread, away from the main thread. Color and background-color changes repaint the element. Anything that touches font-size, letter-spacing, width or margin recomputes geometry, and paint and composite then run again (web.dev).

Animated elements work best on their own compositor layer, but web.dev warns that every layer costs memory and management, and that on devices with limited memory the cost can outweigh the benefit. A per-character effect on a long paragraph multiplies those layers.

No published threshold says how many spans is too many. Test on a mid-range phone, not on the machine you built it on.

Layout shift is the other cost. Cumulative Layout Shift counts unexpected movement, and web.dev defines a good score as 0.1 or less at the 75th percentile of page loads.

Typed or rotating text with no reserved space causes it. So does animating width or height instead of using transform: scale(), and so does a late font swap that changes line breaks after the split.

Reserve space with min-height or min-width on the container, and pre-place the characters as in the typewriter build above.

The Chrome DevTools Performance panel shows both problems. Record the animation, then look for frames longer than 16.7 ms and for flagged layout shifts.

How do you make animated text accessible?

Check the reduced-motion setting before animating, keep one readable copy of the text for screen readers, and give looping effects a pause control.

Reduced motion

The prefers-reduced-motion media query is Baseline Widely available since July 2022 and has worked in every major browser since January 2020 (MDN, web-features data).

Its value is reduce, not none. Users expect non-essential motion to go, so the safe version of a typewriter is the finished text shown at once, or a short fade.

Screen readers

Wrapped letters get read one by one. In 2020, Adrian Roselli found the Awwwards site putting every letter in its own div, which screen readers announced letter by letter.

The usual fix goes through ARIA, with aria-label on the parent and aria-hidden on each span. GSAP’s SplitText does this by default, and its documentation says the setting works for the majority of screen readers.

Roselli’s February 2026 tests disagree. His results table lists the text announced correctly in only 2 of 8 screen reader and browser pairings, and JAWS with Chrome never announced it.

The sources diverge on one detail. According to Roselli, GSAP’s demo wraps text in a div, a generic element that prohibits aria-label, while the docs’ example code uses headings, which allow it.

GSAP’s Cassie Evans replied that she would review the docs. She also named two other routes, a screen-reader-only duplicate of the text and reverting the split once the animation ends.

For one-time reveals, reverting is the sturdiest option, because the accessible copy is then the real text and nothing depends on aria support. Looping effects need the duplicate.

WCAG 2.2 requirements

Under the standards for web accessibility, 2.2.2 Pause, Stop, Hide (Level A) requires a control for moving content that starts automatically, lasts more than five seconds and sits beside other content (W3C).

Rotating words and endless typewriter loops fall under it. A reveal that finishes within five seconds does not.

2.3.3 Animation from Interactions (Level AAA) covers motion triggered by hover or scroll, which users must be able to disable unless the motion carries the content itself. W3C’s guidance lists honoring the reduced-motion preference as one way to meet it.

When does text animation break or hurt the page?

Per-letter splitting breaks joined scripts. Arabic, Persian, Urdu and Syriac letters change shape depending on their neighbors, so one span per letter leaves disconnected letterforms, according to the Framer marketplace documentation for a shape-safe animation component.

The same page says per-letter spans also drop Latin ligatures such as fi and ffl. For these scripts, split by word or line, or animate a mask over unsplit text.

Kerning goes too. Letter-level inline-block spans stop the browser from kerning across glyph pairs, so tight pairs loosen and the line visibly shifts at the moment of the split.

Griffo’s splitText documentation calls this visual jumping and measures character positions before splitting to apply margin corrections. Word-level splits keep kerning inside each word.

Animate the headline, not the paragraph under it. A reader scanning for an answer waits on every character that has not appeared yet.

Low-power hardware is the last problem. MDN notes that reducing animation also helps people on low battery or low-end devices, and a per-character effect that runs smoothly on a desktop is the first thing to cut on a budget phone.

Leave the effect off error messages, prices and legal notices. Navigation labels and anything else visitors read on every visit don’t need it either, and neither does any spot where a plain fade does the same job.

JavaScript Text Animation Examples To Check Out

Laser Writing Text Animation

See the Pen
Laser Writer
by Johan Karlsson (@DonKarlssonSan)
on CodePen.

Dark Text Animation

See the Pen
Dark Text Animation
by Álvaro (@alvarotrigo)
on CodePen.

Shattering Text Animation

See the Pen
Shattering Text Animation
by Arsen Zbidniakov (@ARS)
on CodePen.

Interactive Variable Text

See the Pen
Interactive Variable Text
by JennyBKowalski (@JennyBKowalski)
on CodePen.

THREE JS Text Animation

See the Pen
THREE Text Animation #1
by Szenia Zadvornykh (@zadvorsky)
on CodePen.

Crossing on Scroll

See the Pen
Crossing on scroll
by Álvaro (@alvarotrigo)
on CodePen.

Solid

See the Pen
Solid
by Cyd Stumpel (@Sidstumple)
on CodePen.

Hand written SVG text animation

See the Pen
Hand written SVG text animation
by Matthew Ellis (@mellis84)
on CodePen.

Text animation with Snap SVG

See the Pen
Text animation with Snap SVG
by alticreation (@alticreation)
on CodePen.

Resizable Logo

See the Pen
Resizable Logo! | @keyframers 3.11
by @keyframers (@keyframers)
on CodePen.

Mouse Following Text Animation

See the Pen
Mouse Following Text Animation
by Aykut Kapısız (@aykutkapisiz)
on CodePen.

Text typing animation effect HTML, CSS & JS

See the Pen
Text typing animation effect HTML, CSS & JS
by Álvaro (@alvarotrigo)
on CodePen.

Variable Font Animation

See the Pen
Variable Font Animation
by Tanner Dolby (@tannerdolby)
on CodePen.

Cool SVG Text Animation

See the Pen
SVG text animation
by Cassie Evans (@cassie-codes)
on CodePen.

Unbreakable Kimmie Schmidt

See the Pen
Unbreakable Kimmie Schmidt
by groovc (@groovc)
on CodePen.

In Too Deep

See the Pen
IN TOO DEEP
by Alex Trost (@a-trost)
on CodePen.

Extended

See the Pen
EXTENDED
by Chris Gannon (@chrisgannon)
on CodePen.

Scene.js Kinetic Typing Text Animation

See the Pen
Scene.js Kinetic Typing Animation
by Daybrush (@daybrush)
on CodePen.

Motion Path Scaling

See the Pen
Motion path scaling
by Michelle Barker (@michellebarker)
on CodePen.

“No Time To Die” Title Sequence

See the Pen
“No Time To Die” Title sequence
by Nick Watton (@2Mogs)
on CodePen.

Canvas JavaScript Text Animation

See the Pen
JavaScript text animation on <canvas>
by Mengru Zhang (@MengruZhang)
on CodePen.

3D Kinetic Typography

See the Pen
Kinetic Letters ✨
by Anna Zenn Scavenger (@ScavengerFrontend)
on CodePen.

Neumorphism Typo

See the Pen
Neumorphism Typo
by ilithya (@ilithya)
on CodePen.

JavaScript Intro Text Animation

See the Pen
Intro text animation
by Sampath Paravasthu (@SampathParavasthu)
on CodePen.

Spotlight Cursor Text Screen

See the Pen
Spotlight Cursor Text Screen
by Caroline Artz (@carolineartz)
on CodePen.

Outlines and Overprints

See the Pen
Outlines and overprints
by Jason Pamental (@jpamental)
on CodePen.

JavaScript Letter Animation

See the Pen
Letter Animation
by Florin Pop (@FlorinPop17)
on CodePen.

JavaScript Text Color Draw Animation

See the Pen
text color draw
by CJ Gammon (@cjgammon)
on CodePen.

Animated SVG Signature

See the Pen
Animated SVG Signature
by Silvestar Bistrović (@CiTA)
on CodePen.

Landing Animation for BBAE

See the Pen
[WIP] Landing Animation for BBAE
by Dolphin Wood (@idiotWu)
on CodePen.

JavaScript Circle Text Typing Animation

See the Pen
SVG text typing along a spiral with JS
by Margus Lillemägi (@VisualAngle)
on CodePen.

Anime.Js Logo Animation

See the Pen
anime.js logo animation
by Julian Garnier (@juliangarnier)
on CodePen.

JavaScript Magic Text Animation BaffleJS

See the Pen
JavaScript Magic Text Animation BaffleJS
by MIMAXUZ (@MIMAXUZ)
on CodePen.

Corrupted, Grungy, Retro Text and CRT Effect

See the Pen
Corrupted, Grungy, Retro Text and CRT Effect
by Jacob Foster (@Alca)
on CodePen.

JavaScript Text Animation FAQ

Does animated text hurt indexing?

Not when the final text ships in the page’s HTML. Google indexes from rendered HTML after running scripts (Google Search Central), so text built only by script waits for that rendering step.

Put the real text in the markup and animate on top of it.

How do you animate text in React?

Run the animation inside an effect or hook, never during render, and clean it up on unmount. GSAP’s SplitText integrates with the useGSAP hook, and Motion ships React components through its motion/react entry point.

What stagger delay works for a text reveal?

No official standard exists. Pick the total reveal time first, then divide by the number of units: one second across 40 characters means 25 ms per character.

On longer lines, switch from characters to words so the reveal stays quick.

How can you pause, loop or restart a text animation?

Web Animations API objects expose play, pause and reverse methods plus a finished promise, and GSAP timelines add pause() and restart(). Looping is an iteration option in the first case and a repeat value of -1 in the second.

Are Lettering.js and Textillate.js still worth using?

Only on a site that already loads jQuery. Both are jQuery plugins, so anywhere else they add a dependency for work that Splitting.js or a few lines of native script handle.

Choosing a First Effect and Testing It Before Launch

A first JavaScript text animation should be a one-time reveal on a single headline, split by word and driven by CSS with script-set delays.

The trade-off is flair. That scope rules out per-letter flourishes and endless loops.

Build the reveal and the reduced-motion fallback together, run a screen reader pass before adding polish, and profile frames and layout shift on a mid-range phone last.

The order follows cost of change. Splitting structure and the fallback shape every later step, while profiling only tunes them.

Verified October 13, 2026: the advice to revert the split after the effect holds until GSAP publishes the screen reader results table its team floated in reply to Roselli’s February 2026 post.

Before launch, run the finished page through a web accessibility checklist.

Bogdan Sandu
Latest posts by Bogdan Sandu (see all)