The editor
Split text
A heading that arrives letter by letter is one element per letter. Split a line of text and each letter or word becomes an element of its own, which the stagger step, the presets and every export dialect already know how to animate. The export gives you the split markup back.
- Pick a line, then split it
- Double-click a component and pick the heading itself. Its inspector opens with split text: choose Letters or Words, or use ⌘K “Split text into letters”. Each piece becomes a row in the layers, and they arrive selected, so the next preset with a stagger step lands on all of them one after another. Off (or ⌘K “Unsplit text”) puts the text back. Each is one undo.
- Only an element that holds only text
- Picked elements are addressed by their position among their siblings. Wrapping the text of a node that also holds other elements would shift every sibling after it, and your existing picks would quietly land on the wrong things. A node with nothing but text has nothing to shift, and it’s the case you want anyway. Anything else is refused with a message, and nothing changes.
- Letters sit inside their words
- Each word is a
<span>that doesn’t wrap, and each letter is aninline-blockspan inside it, since a transform does nothing to an inline box. The word spans keep a line break from landing mid-word (“Motion de / sign”), so the heading wraps exactly where its text did. - Still one string to a screen reader
- The pieces are
aria-hidden, and the element ends with its text once more in a visually hidden<span>, using the usual screen-reader-only style written out so it needs no framework. That’s true in the preview and in every export. A screen reader reads the heading, not its spelling, on any element. Anaria-labelwould be skipped on a plain<p>or<div>by several screen readers. - The export writes the letters in
- The code animates spans your component doesn’t have yet, so the export writes them in. CSS and GSAP hand your component back with the letters written into the element, in the Markup block above the code. A raw HTML paste, or a split the export can’t place, gets a Markup block of its own instead: just the element written out split, with its own copy button, in HTML if you pasted HTML and JSX otherwise. Motion writes the letters into the element where it stands in your markup, each a
motion.span, on the stagger variant or with its own props when the letters don’t share one animation; a split it can’t place comes as a JSX block. No dialect uses a plugin. GSAP’s own answer would be SplitText; this is plain markup and the library you chose. - One rule for every letter
- A stagger over a split is recognized as the split’s, even though letters in different words aren’t siblings, and it’s addressed with one selector:
> span:where([aria-hidden]) > spanfor letters and> span:where([aria-hidden])for words. The hidden copy is never animated, and:whereadds no specificity, so each piece’s own delay still wins. CSS keeps each letter’s exact delay, GSAP runs one tween withstagger, and Motion usesstaggerChildren. The parity gate measures all three against the markup the export writes.
Where it stops. Up to 120 letters or words, and 1000 characters: a line, not a paragraph. Switching between letters and words replaces the pieces and any keyframes on them (one undo brings them back). Change the words in Edit source and the split stops applying: the element shows its new text, and its old letters are flagged in the layers as elements that couldn’t be followed. Turn split text off and on again to fix it. A stagger that runs backwards, last letter first, is exported as one animation per letter, because Motion and GSAP both stagger in document order. If you write the markup by hand, keep aria-hidden on each word: the selectors use it to tell a word from the hidden copy.