aa-split
aa-split splits an element into .aa-word, .aa-char, or .aa-line parts and stops there. Use it when you want a CSS-only stagger, a hover effect, or you're driving the animation yourself. Powered by GSAP's SplitText plugin.
aa-split is a single attribute with one mode token plus optional mask and index flags (order-independent).
| Token | Effect |
|---|---|
words | Split into .aa-word parts. |
chars | Split into .aa-char parts (direct children of .aa-line; no .aa-word wrappers). |
lines | Split into .aa-line parts. |
lines-words | Split into words, group by line for cascading line waves. |
lines-chars | Split into chars, group by line for cascading line waves. |
mask (flag) | Wrap each line in overflow: clip (.aa-line-mask) for slide-up reveals. Always line-level — per-char or per-word masks trap the moving unit. |
index (flag) | Expose 1-based --char / --word / --line CSS variables on each split unit for CSS-driven stagger calcs. |
Config attributes
Section titled “Config attributes”| Attribute | Default | Notes |
|---|---|---|
aa-split | (required) | Mode token + optional mask / index flags. Example: "chars mask index". |
aa-stagger | (off) | Seconds, or "<unit> <line>". One value = unit stagger only. Two = unit then line. |
lines-chars and lines-words keep the chars/words split but group the animation by line: chars within a line stagger by the first value of aa-stagger, while each line's tween starts the second value later than the previous one — e.g. aa-stagger="0.02 0.2" spaces chars 0.02s apart and lines 0.2s apart. A single value reuses itself for both.
| shorthand and -sm / -md / -lg / -xl suffixes work like elsewhere — e.g. aa-split="words|lines" splits to words on mobile and lines from md up.
Required GSAP plugins
Section titled “Required GSAP plugins”gsap, SplitText.
Descender padding
Section titled “Descender padding”Anything that clips per line — aa-split="lines mask" (which wraps each line in .aa-line-mask) and the built-in line-clipping animations (text-slide-*, text-tilt-*, text-blur-up, text-blur-down, text-oval-*) — uses overflow: clip at the line box. At tight line-height values the bottom edge sits on the baseline and slices descenders (g, j, p, q, y).
The companion CSS pads each clipped line by 0.15em at the bottom and offsets the wrapper with a matching negative margin so the visual line spacing stays identical. Override globally on :root or per-element by setting --aa-line-mask-pad:
/* Fonts with deeper descenders (display serifs, italics) */:root { --aa-line-mask-pad: 0.22em; }
/* Or per element */.my-headline { --aa-line-mask-pad: 0.22em; }
/* Opt out entirely */.flat-cap-headline { --aa-line-mask-pad: 0; }Live demo — words
Section titled “Live demo — words”The headline below is split into words. Hover any word — pure CSS picks up the .aa-word class to apply a per-word transform.
Hover any word in this headline.
Live demo — lines + mask
Section titled “Live demo — lines + mask”aa-split="lines mask" wraps each line in a clipped wrapper. On hover, CSS slides each line up out of the mask.
Hover this headline. Each line slides upward, clipped by its mask wrapper.
Live demo — CSS-driven stagger with index
Section titled “Live demo — CSS-driven stagger with index”Adding index to aa-split sets a 1-based --char (or --word / --line) custom property on every split unit. CSS can then derive a transition-delay per unit without any JavaScript stagger. Hover the button below — each character translates up with a delay calculated from var(--char).
<a class="split-button" href="#"> <span class="split-button__inner"> <span class="split-button__text" aa-split="chars index">Hover me</span> </span></a>.split-button { --shine: #ef2528; display: inline-grid; padding: 0.75em 1.25em; border-radius: 2.5em; background: #fff; color: #131313; font-weight: 600; text-decoration: none;}.split-button__inner { display: flex; justify-content: center; }/* The text wrapper clips chars at the baseline so they appear from below. */.split-button__text { clip-path: inset(0% 0% -15%); }
.split-button .aa-char { text-shadow: 0 1.1em var(--shine); transition: translate 0.3s cubic-bezier(0.625, 0.05, 0, 1), text-shadow 0.1s 0.15s ease;}@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) { .split-button:hover .aa-char { text-shadow: 0 1.1em currentColor; translate: 0 -1.1em 0; /* var(--char) is set on each char by aa-split="chars index" */ transition: translate 0.5s calc((var(--char) - 1) * 0.024s) cubic-bezier(0.625, 0.05, 0, 1), text-shadow 0.175s calc((var(--char) - 1) * 0.024s + 0.25s) ease; }}Credit to Osmo Supply's Button 032 for the recipe.
Live demo — combining with text-fade-up
Section titled “Live demo — combining with text-fade-up”Override the animation's default split granularity by adding aa-split to a text animation. Each block below uses the same text-fade-up animation on a three-line heading and only differs by aa-split.
aa-split="lines"A three-line headline that fades up one line at a time, staggered top to bottom across the heading.
aa-split="words"A three-line headline that fades up one word at a time, staggered through the whole heading.
aa-split="chars"A three-line headline that fades up one character at a time, staggered through the whole heading.
aa-split="lines mask"A three-line headline that fades up one line at a time, each line clipped by its mask wrapper.
aa-split="lines-chars" aa-stagger="0.02 0.2"A three-line headline where chars within each line burst in together while the lines themselves cascade.
aa-split="lines-words mask" aa-stagger="0.05 0.25"A three-line headline. Words stagger inside each line and lines slide in clipped by their mask wrapper.