Pools of colour swirl and flow inside the letters, for headings that feel alive without moving an inch.
Liquid light
Customize
Usage
import { LiquidGradientText, liquidPalettes } from "@/components/motionkit/liquid-gradient-text";
<LiquidGradientText
as="h1"
fontSize={48}
fontWeight={700}
>
Liquid light
</LiquidGradientText>Motion
The exact curves this component runs, plotted with Motion's own maths.
Flow
Runs on its own
Works in the App Router as is. Requires Tailwind CSS v4.
npm install motionSave as components/motionkit/liquid-gradient-text.tsx.
"use client";
import type { CSSProperties, ElementType, ReactNode } from "react";
import { motion, useReducedMotion, useTime, useTransform } from "motion/react";
/** Ready-made palettes. Pass one to `colors`, or any array of two to five colours. */
export const liquidPalettes = {
aurora: ["#A855F7", "#6366F1", "#22D3EE", "#C084FC"],
sunset: ["#F97316", "#EC4899", "#A855F7", "#FACC15"],
ocean: ["#22D3EE", "#3B82F6", "#14B8A6", "#A5F3FC"],
ember: ["#EF4444", "#F59E0B", "#FDE68A", "#B91C1C"],
petrol: ["#5FAEB4", "#E4DCCF", "#A56D5B", "#0E7C86"],
} satisfies Record<string, string[]>;
type LiquidGradientTextProps = {
children: ReactNode;
/** Two to five colours that flow through the letters. */
colors?: string[];
/** Direction of the flow in degrees: 0 runs left to right, 90 top to bottom. */
angle?: number;
/** Playback speed. 1 is a gentle flow; 2 is twice as fast. */
speed?: number;
/** Font size as a CSS length, or pixels when a number. */
fontSize?: number | string;
fontWeight?: number;
/** Element to render, e.g. "h1". */
as?: ElementType;
className?: string;
};
export function LiquidGradientText({
children,
colors = liquidPalettes.aurora,
angle = 0,
speed = 1,
fontSize,
fontWeight,
as: Tag = "span",
className = "",
}: LiquidGradientTextProps) {
const reduceMotion = useReducedMotion();
const time = useTime();
const radians = (angle * Math.PI) / 180;
// two layers, recomputed each frame: a broad gradient that flows along `angle`, and a pool of
// light per colour circling on its own path, which is what makes it read as liquid, not a slide
const backgroundImage = useTransform(time, (ms) => {
const t = reduceMotion ? 0 : (ms / 1000) * speed;
const pools = colors.map((color, i) => {
const phase = (i / colors.length) * Math.PI * 2;
const x = 50 + Math.cos(t * 0.6 + phase) * 38 + Math.cos(radians) * Math.sin(t * 0.35 + i) * 14;
const y = 50 + Math.sin(t * 0.8 + phase) * 32 + Math.sin(radians) * Math.sin(t * 0.35 + i) * 14;
return `radial-gradient(circle at ${x}% ${y}%, ${color}, transparent 58%)`;
});
// the base runs through the palette twice so sliding it by half its size loops seamlessly
const shift = (t * 12) % 100;
const base = `linear-gradient(${angle + 90}deg, ${[...colors, ...colors, colors[0]].join(", ")})`;
return { pools: pools.join(", "), base, position: `${50 + Math.cos(radians) * (shift - 50)}% ${50 + Math.sin(radians) * (shift - 50)}%` };
});
const layers = useTransform(backgroundImage, (value) => `${value.pools}, ${value.base}`);
const positions = useTransform(backgroundImage, (value) => `${colors.map(() => "0% 0%").join(", ")}, ${value.position}`);
const sizes = `${colors.map(() => "100% 100%").join(", ")}, 200% 200%`;
const style: CSSProperties = {
fontSize: typeof fontSize === "number" ? `${fontSize}px` : fontSize,
fontWeight,
};
return (
<Tag className={className} style={style}>
<motion.span
className="bg-clip-text text-transparent [-webkit-box-decoration-break:clone] [box-decoration-break:clone]"
style={{ backgroundImage: layers, backgroundPosition: positions, backgroundSize: sizes, backgroundRepeat: "no-repeat" }}
>
{children}
</motion.span>
</Tag>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | The text. |
| colors | string[] | liquidPalettes.aurora | Two to five colours. Presets: aurora, sunset, ocean, ember, petrol. |
| angle | number | 0 | Direction of the flow in degrees. |
| speed | number | 1 | Playback speed multiplier. |
| fontSize | number | string | — | Pixels, or any CSS length. |
| fontWeight | number | — | Font weight, e.g. 700. |
| as | ElementType | "span" | Element to render, e.g. "h1". |
Think beyond ordinary.
Gradient Text
Headline text with a slowly flowing, seamlessly looping gradient.
Build faster, smarter, together
Morphing Text
Rotates through a list of words, letters rising in and drifting out.
Make every word land.
Kinetic Headline
Headlines that arrive word by word or letter by letter: slide, blur, hinge or rise from behind a mask, from any direction.