Text Effects
Gradient Flow Text
A heavy headline used as a stencil over a travelling spectrum, looped seamlessly by matching the first and last colour stops.
Original by Plinth · MIT
Report a problem or suggest an improvementInstall
npx shadcn@latest add https://plinthui.com/r/gradient-flow-text.json// No hooks or handlers, so this stays a server component — it is pure CSS.
// background-clip: text with a moving gradient is outside what Tailwind can
// express, so the CSS ships as a string.
const css = `
.flow {
margin: 0;
font: 800 clamp(34px, 9vw, 68px)/1.06 system-ui, sans-serif;
letter-spacing: -0.035em;
text-align: center;
max-width: 12ch;
/* The gradient is painted on the element, then clipped to the glyph shapes.
This is why the colour can move independently of the text: the type is a
stencil over a moving surface, not coloured letterforms. */
background-image: linear-gradient(
100deg,
#ff3d81 0%,
#ff9f45 18%,
#ffe45e 34%,
#4affd0 52%,
#5b8cff 70%,
#b45bff 86%,
#ff3d81 100%
);
/* 300% wide so there is somewhere to travel. At 100% the gradient is exactly
the element and sliding it would drag its own ends into view. */
background-size: 300% 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
animation: flow-run 9s linear infinite;
}
/* The first and last stops are the SAME colour. The gradient therefore meets
itself at the wrap point, so translating by exactly one full width returns
an identical image and the loop has no seam. Different end stops give a
visible colour jump once per cycle, which is the usual bug here. */
@keyframes flow-run {
from { background-position: 0% 50%; }
to { background-position: 300% 50%; }
}
/* A soft bloom behind the type, so the colour looks emitted rather than
filled in. Sits behind because the text itself is transparent. */
.flow-stage {
position: relative;
display: grid;
place-items: center;
padding: 40px 28px;
isolation: isolate;
}
.flow-stage::before {
content: "";
position: absolute;
inset: 12%;
background: radial-gradient(60% 60% at 50% 50%, rgba(255, 90, 170, 0.28), transparent 70%);
filter: blur(28px);
z-index: -1;
pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
.flow { animation: none; background-position: 50% 50%; }
}
`;
export function GradientFlowText({ children = 'Ship it in colour' }: { children?: React.ReactNode }) {
return (
<>
<style>{css}</style>
<div className="flow-stage">
<h1 className="flow">{children}</h1>
</div>
</>
);
}