CSS Gradient Text Generator

Paint a gradient through text with background-clip, in the declaration order that leaves legible text when a browser does not support it.

Enable JavaScript to change the gradient; the CSS below is for the defaults.

Gradient

Used by linear and conic gradients. A radial one is positioned rather than angled.

Leave empty for a two-colour gradient.

Written before the transparent one, so a browser without background-clip: text shows solid text rather than nothing.

Headlines take a gradient well

The same gradient at body size, which is where it has to earn its place.

A contrast checker cannot measure this: the text is two or three colours at once. Pick a gradient whose darkest and lightest points both pass against the background.

gradient-text.css
.headline {
	/* A browser without background-clip: text stops here, with legible text. */
	color: rgb(30 86 236);
	background-image: linear-gradient(92deg, rgb(30 86 236), rgb(124 58 237), rgb(226 69 107));
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	-webkit-text-fill-color: transparent;
	display: inline-block;
}

Gradient text is four declarations and one idea: paint a gradient as the element’s background, clip that background to the shape of the glyphs, and make the text itself transparent so the background shows through.

The order of those declarations is the part that gets skipped. A solid colour has to come before the transparent one, because a browser that does not understand background-clip: text will otherwise render transparent text over a background it has also painted, which is invisible text. Written in the right order, the fallback is legible text in a solid colour.

The other thing worth saying plainly: no contrast checker can measure this. The text is two or three colours at once. Pick a gradient whose darkest and lightest points both pass against the background, and check them separately.

How to use

  1. Choose the gradient kind and its colours. Leave the middle colour empty for a two-stop gradient.
  2. Set the fallback colour. It is the one people see if the clip is not supported, so make it one of the gradient’s own colours.
  3. Keep “inline-block” on for anything that might wrap, unless the element is already a block.

Example

.headline {
	/* A browser without background-clip: text stops here, with legible text. */
	color: rgb(30 86 236);
	background-image: linear-gradient(92deg, rgb(30 86 236), rgb(124 58 237), rgb(226 69 107));
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	-webkit-text-fill-color: transparent;
	display: inline-block;
}

Two color declarations in one rule is not a mistake. The first is the fallback and the second overrides it wherever the clip works, which is how the technique degrades safely.

Pitfalls

Order is the whole technique. Solid colour first, transparent second. Reversed, an unsupporting browser shows nothing.

Safari needs -webkit-text-fill-color. Setting color: transparent alone is not enough there, and the prefixed background-clip is still worth shipping.

Inline elements clip per line. A gradient across a heading that wraps restarts on every line, because an inline box is really several boxes. display: inline-block gives it one box to work with.

It cannot be contrast checked. Tools measure one foreground colour. Test the darkest and lightest points of the gradient against the background separately, and keep both above the ratio you need.

Selection and highlight look odd. Selected text usually keeps the transparent fill, so the selection rectangle appears with nothing legible in it. Set a ::selection colour if that matters.

It is a headline effect. At body size a gradient mostly reads as inconsistent weight. The preview shows both sizes for exactly that reason.

Keep it off long text. The effect costs a paint of the background box behind every glyph, and applied to a page of copy it is both slow and hard to read.

Compatibility

background-clip: text is supported unprefixed in Chrome 120, Safari 14 and Firefox 49, with the -webkit- prefix working further back in every WebKit and Blink browser. Both are emitted.

Older browsers land on the fallback colour, which is the first color declaration, and see ordinary solid text. That is the entire point of the ordering, and the test suite asserts the solid declaration appears before the transparent one rather than trusting it to stay that way.

The gradient is built by one function, mirrored in PHP for the first paint, and a case file compares the two on every build. One case has a blank colour field, which is what caught a difference between the languages: JavaScript threw on it and PHP silently produced black, so both now fall through to the next colour that is set.

A single-stop gradient is invalid CSS, so a blank field can never produce one: the tool repeats the remaining colour instead.

Frequently asked questions

Why are there two color declarations?
The first is a fallback for browsers without background-clip: text; the second makes the text transparent so the gradient shows. A browser that understands the clip applies both and the second wins.
Why does my gradient restart on every line?
The element is inline. Add display: inline-block, which the tool does for you, or apply the effect to a block-level element.
Is gradient text accessible?
The text is still text, so screen readers and search engines are unaffected. Contrast is the problem: no tool can measure it, so check the extremes of the gradient by hand.
Can I animate the gradient?
You can animate background-position on a larger gradient, which is the usual trick. Animating the colour stops themselves needs @property registration, which is supported in current browsers and not in older ones.
Does this work on a <button> or an input?
On a button, yes. On an <input>, generally not: the value text is rendered by the widget rather than as a normal text node, and WebKit does not clip it.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.