I had a client last week who wanted a specific “pop” on their landing page. They were launching a toon-themed product line and wanted headers that looked like a 1960s comic book: high-contrast strokes, staggered letters, heavy shadows. My first thought was that we were about to do the manual span dance again.
The technical problem is that CSS still has no :nth-letter selector. Style every character differently and you end up pulling in a JS library that splits the text into individual <span> elements. That trick is old, and it usually wrecks accessibility, because screen readers see those spans and announce the header one letter at a time. “H… E… L… L… O.” It is a bad experience for users, and it is lazy on our part.
Using an accessible typography generator
Andy Clarke has a tool called the Toon Title Text Generator, and it is a solid piece of work. It generates the CSS for the fun typography: strokes, shadows, the whole bit. The part that got my attention was not the visual generator, though. It was how he handles the markup splitting with a tool called Splinter.js, which uses ARIA attributes instead of dumping bare spans into the DOM, so assistive technology can still read the heading.
That is a long way ahead of the hacky text-effect setups I keep running into. It follows the same direction as the real accessibility improvements in WordPress core lately, away from patches and toward semantic markup. Put aria-label on the parent and aria-hidden="true" on the individual spans, and you keep the visual flexibility without the screen reader problem.
The tool sits over at CSS-Tricks and lets you tweak font, stroke and letter spacing on the fly. It outputs the CSS you need, and paired with Splinter.js that is most of the job done. If you already run GSAP’s SplitText you may have some of this covered, but on smaller projects Clarke’s version is much leaner.
Implementing the markup
When I build this into a custom WooCommerce theme, I wrap it in a PHP function so the client never has to touch the ARIA attributes. That keeps the CSS architecture clean and the HTML robust at the same time. A helper function might be structured like this:
/**
* Render an accessible toon-style title.
*
* @param string $text The header text.
* @param string $class Additional CSS classes.
* @return string The formatted HTML.
*/
function bbioon_render_toon_title( $text, $class = '' ) {
if ( empty( $text ) ) {
return '';
}
$chars = str_split( $text );
$output = sprintf(
'<h2 class="bbioon-toon-header %s" aria-label="%s">',
esc_attr( $class ),
esc_attr( $text )
);
foreach ( $chars as $char ) {
if ( $char === ' ' ) {
$output .= '<span aria-hidden="true"> </span>';
} else {
$output .= sprintf(
'<span class="toon-char" aria-hidden="true">%s</span>',
esc_html( $char )
);
}
}
$output .= '</h2>';
return $output;
}
Performance and semantics
Performance is usually the catch. Put fifty of these headers on one landing page and you will feel the weight of all those DOM nodes. Use it on main headings only, not on every piece of UI text, because overdoing it is a quick way to wreck your Core Web Vitals.
This gets complicated fast once you care about more than “making it look cool.” If you are tired of debugging someone else’s messy front-end code and you want a site that is accessible and fast, drop me a line. I have probably fixed this exact issue three times this month already.
So, are you still hand-writing spans for typography effects, or have you moved to something automated and accessible?