Text Effects
component / motion
Nine CSS-only text effects — gradient, shimmer, rotating words, typewriter, split reveal, marker highlight, glitch, outline and extruded depth — with no animation library and no client boundary.
- type
- component
- files
- 2
- registry deps
- none
- npm deps
- none
installs
- components/automindz/text-effects.tsx
- styles/automindz-text.css
- Import styles/automindz-text.css once in your layout
- No npm dependency, no client JS — CSS only
- Base state is the finished state: no stylesheet, plain text
- Decorative layers are off inline, where Tailwind preflight cannot reach
- SplitText's effect prop is ten named reveal variants in one component
The stylesheet is not optional: add import "@/styles/automindz-text.css" to your root layout once, or nothing moves. Skipping it is safe and never blank — every effect renders as plain, correct text, which is also the reduced-motion, no-support and headless path. RotatingText takes words and gives assistive tech only the first (or label); TypingText takes text and is one line that does not wrap; SplitText takes text, reveals against its own view() timeline and picks its arrival with effect (rise, blur, fade, scale); GlitchText takes text because its ghosts are attr() on a data attribute. GradientText, ShimmerText and OutlineText paint through background-clip: text and restore real ink under forced-colors. DepthText is static by design and never moves. Every dial is an --automindz-* custom property, listed at the top of the stylesheet — except --automindz-layer, which is the on/off switch for the decorative layers and is not yours to set.
Variations
one state per section
- 01GradientText, still and sweeping
- 02ShimmerText on a label and a heading
- 03RotatingText inside a sentence
- 04TypingText with a caret
- 05SplitText, per word and per character
- 06HighlightText sweeping behind a phrase
- 07SplitText: the four arrivals
- 08GlitchText, with the real words never moving
- 09OutlineText, hollow and filling
- 10DepthText: extruded and echoed
- 11The degraded state: no stylesheet, or reduced motion
GradientText, still and sweeping
Both headings are painted through a background, which is the risk: transparent ink over a background that did not paint is an empty line. The gradient only exists inside an @supports guard for background-clip, and a forced-colors block puts real ink back — turn on Windows High Contrast or Increase Contrast and both headings go solid rather than blank. Every stop is a color-mix of primary or accent into foreground, so the ramp stays near the ink your theme already picked.
Ship the boring half
Ship the boring half
The second one sweeps. Under prefers-reduced-motion the gradient stays and only the movement stops — the gradient is the effect, the movement is the flourish.
ShimmerText on a label and a heading
The glint is a mix with currentColor, never a replacement, so the brightest point of the pass still carries half the colour you set. Unlike the gradient the whole treatment is behind prefers-reduced-motion, not just the animation: a frozen shimmer is a word that is randomly brighter in the middle, which says nothing. Stopped means plain text here.
Now in the registry
Six effects, no animation library
No npm dependency, no client boundary, no JS animation loop. Both lines above are one span and a class name.
RotatingText inside a sentence
Watch the line either side of the word: it never twitches, because the words are grid items sharing one cell, so the box is the widest word without anything being measured. Exactly one word is on screen at any instant — the opacity is binary, not a crossfade — and the last word hands back to the first with no seam. Only the first word is real text; the stack is aria-hidden, so nobody hears the list read out as a pile.
Install SSO in one command
Try selecting the sentence and copying it. You get “Install SSO in one command” — one word, not five, because the rotating layer is user-select: none and the real word is a plain text node.
TypingText with a caret
Reload to watch it type. The reveal clips a percentage of the line's own box rather than animating a width in ch, so the last step is exactly the whole line in any font — a ch-width typewriter overshoots or stops short in anything but monospace. The mono line below is the version where one step is genuinely one character. The caret's blink is floored at 1.25Hz in the stylesheet, so the dial can only make it slower.
Everything is already built.
npx shadcn@latest add @automindz/text-effects
Both lines are real text in the DOM. The clipped copy and the caret are aria-hidden, so a screen reader reads each line once, whole.
SplitText, per word and per character
Scroll the panel. One timeline on the container drives every part, so a heading arrives as one gesture and not as forty unrelated events; the stagger moves each part's start only and the last part ends at exactly entry 100%, which is reachable from every scroll position — so the tail of a sentence can never stay invisible. Note the spaces survive in both: whitespace is never split and never animated.
Scroll down. Two headings: the first splits per word, the second per character.
Every state your client app owes, already built
Every state your client app owes, already built
HighlightText sweeping behind a phrase
Scroll the panel and watch the marker draw itself under the words. It is the element's own background, so it sits behind the text with no extra box to stack — and the sweep grows the stroke without ever changing its colour, which is how the contrast holds at every point and not only at the end. The marker is 18% primary mixed into transparent, so the ground under the words stays a few percent from the page's own.
Scroll down. The marker is drawn as the phrase enters, and it wraps with the phrase.
A registry beats regenerating a component per project because the item already carries every state you would have forgotten — pending, failure with a retry, empty, capped and disabled with a reason.
SplitText: the four arrivals
Scroll the panel. rise, blur, fade and scale differ by a keyframe and nothing else, which is why they are one prop rather than four components — between them they cover the ten named reveal variants the animation libraries ship separately. Direction is a dial, not an effect: the last line sets a negative distance and an inline shift, so it arrives from the top right.
Scroll down. Four lines, four arrivals, one component.
GlitchText, with the real words never moving
The ghosts are ::before and ::after reading attr(data-text), so there is exactly one text node here — nothing to un-hide, nothing extra to select. content: attr(data-text) / "" is the alt-text form, which hands assistive tech an empty string instead of reading the heading three times. Each ghost is fully clipped for most of its cycle and the base copy is never dimmed, so the words stay legible at every frame; the rate is floored at 1.2s.
Ship the boring half
401 UNAUTHORIZED
Try selecting the heading. You get the sentence once — pseudo-element content is not selectable and not in the accessibility tree.
OutlineText, hollow and filling
Scroll the panel for the second one. A hollow letter is a transparent fill plus a stroke, and an engine with the fill and no stroke would render an invisible heading — so both live inside one @supports that tests for both, and the forced-colors floor puts solid ink back and zeroes the stroke. The filled variant rests filled: no support, reduced motion or no stylesheet all land on solid text, never on an empty outline.
Scroll down. The first stays hollow; the second fills as it enters.
Every state, already built
Every state, already built
DepthText: extruded and echoed
The one effect here that does not move, deliberately. The published versions drive both of these from the pointer, which is a client boundary and an effect that does not exist on a touch screen — this ships the part that is real for every reader. depth keeps one colour for every layer so the copies read as a side wall; echo widens the step and fades the alpha so they read as motion already past. The shadow is always behind the glyphs, so the ink being read is untouched at any layer count.
Ship the boring half
Ship the boring half
Both are one span and a text-shadow stack — no extra copies in the DOM, so selection, find-in-page and a screen reader all see one sentence.
The degraded state: no stylesheet, or reduced motion
This is the single most important thing the item does. Every effect above, rendered exactly as it lands for a consumer who never imported the stylesheet and for a reader with prefers-reduced-motion: reduce — plain, correct, readable text with nothing missing and nothing waiting for a trigger. The rotation shows one word, the typewriter shows its whole line, the split shows its whole sentence, the marker is drawn in full rather than at zero, the glitch ghosts stop existing and the outline goes solid. DepthText keeps its extrusion because it never moved: this panel is the reduced-motion path, and with no stylesheet at all that line is flat too.
Ship the boring half
Install SSO in one command
Everything is already built.
Every state your client app owes, already built — marked in full.
Steady. Solid. Still.
Nothing here is hidden waiting for a scroll, a clock or a support query. The base state is the finished state, which is why a headless render and a browser without scroll-driven animation get a page that reads correctly rather than a page of blanks.
