Back to Articles

Your Text Animations Are Breaking Screen Readers (Here's the Fix)

Your Text Animations Are Breaking Screen Readers (Here's the Fix)

There is a common, well-documented CSS animation pattern that silently breaks screen readers. Most developers and designers who use it have no idea it does this.

The pattern is per-letter animation — splitting text into individual DOM elements to create stagger effects where each letter or word animates in sequence. It looks polished. It has a well-worn implementation path via libraries like GSAP's SplitText plugin. It is genuinely broken for users who rely on assistive technology.

This is not a niche edge case. It affects a significant percentage of your users, and the fix is straightforward.

What per-letter animation does to the DOM

The technique works by taking a string of text and splitting it into individual elements — one <span> or <div> per character. CSS transitions or JavaScript animation libraries then stagger the opacity, position, or transform of each element to create the appearance of text animating in letter by letter.

Here's what that looks like in the DOM (from GSAP's own SplitText demo):

<div aria-hidden="true">
  <div aria-hidden="true">H</div>
  <div aria-hidden="true">e</div>
  <div aria-hidden="true">l</div>
  <div aria-hidden="true">l</div>
  <div aria-hidden="true">o</div>
</div>

The individual characters are wrapped in elements, each with aria-hidden="true". The assumption is that the outer container holds the accessible version of the text via aria-label. GSAP's SplitText documentation describes this approach and provides it as the screen reader solution.

Accessibility expert Adrian Roselli tested it extensively in February 2026 and documented the results clearly: it does not work reliably. In his words, the plugin "asserts screen reader support that doesn't stand up to use."

Why the ARIA fix doesn't hold

The intended implementation requires authors to use SplitText in a specific way that GSAP doesn't enforce. In practice:

  • aria-label is prohibited on elements with a generic role (like <div>) in some contexts
  • Different browser and screen reader combinations handle the pattern differently
  • Multiple screen reader users confirmed letter-by-letter reading in Roselli's testing

Roselli received confirmation from three independent screen reader users that the animation produced broken output — reading the text character by character rather than as a complete word.

This has been a known issue for years. Roselli traces it back to 2012, when similar patterns using <kern> elements for letter spacing had the same problem. In 2020, the AWWWards website wrapped every letter in a <div> for animation — screen readers presented it letter by letter. The technique predates the current library ecosystem, and the problem has followed it.

The broader principle at work here is important: ARIA fixes for broken markup are less reliable than just not breaking the markup in the first place. When you need ARIA to rescue a DOM structure that assistive technology can't parse, you are working against the grain of how accessibility is meant to function. ARIA roles are designed to enhance semantically correct markup, not to paper over structures that were never accessible.

Block letter-splitting unless screen readers pass

The newsletter's recommendation is the right default: block per-letter DOM splitting unless screen reader testing for two browser/reader pairings passes. No proof, no merge.

The practical implementation of this as a team standard:

  • Any PR that introduces per-letter element splitting must include screen reader test results
  • Minimum two combinations: NVDA + Chrome and VoiceOver + Safari cover the majority of real-world usage
  • The test is simple: does the animated text read as a complete word or phrase, or does it read character by character?
  • If character by character, the implementation does not pass

This is not a high bar. It is not asking for extensive accessibility testing on every PR. It is asking for one specific, easily verifiable check on a specific class of implementation.

If you want more thinking like this, Unicorn Club is a free weekly newsletter for senior designers and product teams.

The prefers-reduced-motion issue and why it's not enough

A common response to animation accessibility concerns is to implement prefers-reduced-motion support — disabling or reducing animations for users who have indicated a preference for less motion in their operating system settings.

This matters, and you should implement it. But it doesn't solve the screen reader problem.

prefers-reduced-motion is a motion preference setting. Screen reader users may or may not have it enabled. Many screen reader users have full vision and use assistive technology for other reasons — motor impairments, for example. The animation playing at all is not the problem. The DOM structure is the problem.

A text animation that respects prefers-reduced-motion but still splits the text into individual DOM elements is still broken for screen readers. The two issues are separate:

  • Motion preference: Does the animation play? Controlled by prefers-reduced-motion.
  • DOM structure: Is the text readable by assistive technology? Controlled by how you structure the HTML.

Both need to be addressed. Implementing one does not substitute for the other.

The fix: don't split at the DOM level

The simplest and most reliable fix is to not split words into per-letter elements. This doesn't mean giving up on text animation — it means achieving the same visual effect through a different technical approach.

Approaches that don't break screen reader compatibility:

CSS clip-path or masking: Animate text reveal using clip-path, which affects the visual rendering of text without altering the DOM structure. The text remains as a single element.

Opacity on whole words or sentences: Stagger at the word or sentence level rather than the character level. A word-by-word reveal reads correctly and can achieve a similar visual rhythm.

CSS @keyframes on the container: Animate the container element rather than individual characters. The text inside remains intact in the DOM.

Canvas rendering for decorative elements: If the animation is purely decorative — a hero element with no meaningful text content — render it in a canvas element and provide the text via an accessible adjacent element. This removes it from the document flow entirely.

The point is that per-letter DOM splitting is a technique chosen for developer convenience, not because it's the only way to achieve the visual result. The visual effect is achievable through methods that leave the semantic structure of the text intact.

Every animation decision has an accessibility consequence

The per-letter animation problem is a clear example of a pattern that became widespread through tooling momentum rather than considered design. GSAP's SplitText is popular, well-documented, and used on thousands of production sites. The accessibility failure was baked into the implementation path, and most teams never checked.

That's the broader point. Animation is not an accessibility-neutral decision. Every time you animate a DOM element, you're potentially affecting how assistive technology reads it. The semantic HTML structure that makes content accessible is the same structure that your animation code is operating on.

This means animation decisions belong in the accessibility review, not just the design review. The question isn't only "does it look good?" or "does it respect prefers-reduced-motion?" The question is: what does this do to the DOM, and does that DOM remain readable by the tools that depend on it?

The standard for accessible animation is not that the visual experience is inclusive. It's that the content — the actual information — is accessible regardless of whether the animation plays or not, and regardless of what tool the user is using to access it.

Per-letter animation fails that standard by default. The default should be to not use it until you can prove it doesn't.