Text
Gradient Text
Polymorphic heading text painted with a token-driven gradient, optionally looping — no hex codes anywhere.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/gradient-text.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "GradientText" component (no hooks,
no animation library — it stays a server component).
Contract
- Export a forwardRef element extending React.HTMLAttributes<HTMLElement>.
- Props: as (rendered tag: span | div | p | h1..h6, default "span"),
from / via / to, direction ("r" | "br" | "b", default "r"),
animate (default false), speed (seconds per cycle, default 6).
- from / via / to accept TOKEN NAMES ONLY — "primary" | "foreground" |
"chart-1".."chart-5" — mapped internally to var(--primary) etc. The
consumer can never pass a hex value, so the gradient is always a
projection of the host theme (and dark mode comes free).
- Defaults from="primary" via="chart-2" to="foreground": a trio that keeps
contrast in BOTH color schemes, including monochrome themes where primary
sits close to foreground (the mid stop is what makes the ramp visible).
Set via equal to from or to for a clean two-stop ramp.
Behavior
- Paint the text by putting a linear-gradient in background-image and
clipping it to the glyphs (bg-clip-text + text-transparent). direction
maps to "to right" / "to bottom right" / "to bottom".
- animate={true}: the gradient must loop without a visible jump. Build the
stop list as a palindrome (from, via, to, via, from), set background-size
to 200% 200%, and animate background-position from 0% 0% to 200% 200% —
shifting by exactly one full tile in both axes lands on an identical
picture, so the loop has no seam. Static mode uses the plain three-stop
ramp.
- Ship the keyframes AND the animation rule in one hoisted <style href
precedence="medium"> tag (React 19 dedupes by href). Put the reduced-
motion override in the same stylesheet:
@media (prefers-reduced-motion: reduce) { .cls { animation: none } }
so it wins by source order instead of depending on Tailwind's variant
ordering. The gradient itself stays visible — only the motion stops.
- No hooks, no matchMedia, no "use client": everything is CSS, so the
component renders on the server and costs zero client JS.
Rendering & styling
- Semantic tokens only — the palette is literally a token lookup table;
there is no hardcoded color anywhere in the file.
- The rendered tag is the consumer's choice, so headings stay real
headings (h1/h2) for outline and SEO; styling is identical across tags.
- Merge className via cn() and merge the consumer's style object AFTER the
computed background so per-instance overrides still work.
- Contrast discipline: clipped text has no currentColor, so any decoration
(underline, text-shadow) must be given its own color, and gradients read
best at display sizes — for body copy keep the ramp between tokens that
both pass contrast on your background.
Customization levers
- Palette: from / via / to are the whole design surface — chart-1..5 are a
ramp, so a "cool" or "warm" headline is a one-word change per stop.
- Direction: "r" for headlines, "br" for big display type (the diagonal
reads as depth), "b" for stacked/short words.
- Motion: animate + speed (4-8s reads as ambient; below 3s reads as a
loading indicator). Drop animate for print-like stillness.
- Tag: as="h1" for the page title, as="p" for a lead paragraph, as="span"
to gradient just one phrase inside a sentence.
- Scope: wrap a single word instead of the whole line — nest a <span> child
and give the parent the plain text color for a two-tone headline.Concepts
- Token-only palette — stops are selected by name (
primary,chart-3) and resolved to CSS variables inside the component, so a consumer physically cannot hardcode a brand color; retheming the app retints every gradient headline. - Background clipped to glyphs — the text is transparent and the gradient is painted behind it with
background-clip: text, which is why the ramp follows the shape of the letters instead of a rectangle. - Palindrome ramp — the animated variant mirrors its stops (
A B C B A) so the tile's leading and trailing edges are the same color; shifting the background by exactly one tile loops without the hard seam a plainA B Cramp would show. - Ownership of the reduced-motion rule — the animation and its
prefers-reduced-motionoverride live in the component's own hoisted stylesheet, so the override wins by source order and never depends on where Tailwind places itsmotion-reduce:variant. - Zero client JS — no hooks and no
matchMediameans no"use client": the component streams from the server and the animation is pure CSS. - Polymorphic without losing semantics —
aspicks the rendered tag so a gradient headline is still an<h1>in the document outline; styling is identical across tags.