CSS has no gradient color for text. Gradient text is a gradient background clipped to the shape of the glyphs, with the real text color made transparent so the background shows through. Most problems with the effect come from that: the color you see is a background, and the text color is invisible. The examples below were checked in Chrome.
The basic recipe
.gradient-text {
background-image: linear-gradient(90deg, #7c3aed, #db2777, #b45309);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}Write both clip declarations. The text value began as a WebKit extension, and some browser versions still in use only understand the prefixed form. The prefix costs nothing in browsers that support the standard property.
MDN's background-clip page makes two accessibility points worth repeating. The clipped background has little visible effect if the text is opaque, so the transparency is required. And if the background image fails to load, the text can become unreadable, so give it a fallback.
Keep a readable fallback
If a browser ignores background-clip: text but applies color: transparent, the text disappears and a gradient rectangle is left in its place. Set a solid color first, then switch to transparent only inside a feature query:
.gradient-text {
color: #6d28d9; /* readable on its own */
}
@supports (background-clip: text) or (-webkit-background-clip: text) {
.gradient-text {
background-image: linear-gradient(90deg, #7c3aed, #db2777, #b45309);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
}Pick the fallback color from the gradient, usually its darkest stop, so the heading looks deliberate without the effect.
color: transparent or -webkit-text-fill-color
You can hide the glyph fill with -webkit-text-fill-color: transparent instead of color: transparent. The difference shows when the element contains anything that uses currentColor. In our test, a heading with an inline SVG arrow (stroke="currentColor") lost the arrow completely with color: transparent. With -webkit-text-fill-color: transparent, the arrow kept the element's color while the text still showed the gradient. Use the fill property when headings or links contain icons, or put the gradient on an inner span that wraps only the words.
Fit the gradient to the text
The background covers the element's box, not the text. A block-level heading stretches to the full container width, so a short heading on a wide screen only shows the first part of the gradient. A three-color purple-to-amber gradient on a short h1 can show only purple and pink. Shrink the box to the text:
h1.gradient-text {
width: fit-content; /* or display: inline-block */
margin-inline: auto; /* if the heading is centered */
}For an inline span that wraps across lines, the browser treats the fragments as one long strip by default. The first line gets the start of the gradient and the last line gets the end. To repeat the whole gradient on every line, use box-decoration-break: clone (with its -webkit- prefix):
.gradient-text--per-line {
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
}Clipped descenders and italics
Because the gradient is painted only inside the element's box, anything that extends outside it is invisible: the tails of g, j, p, q, and y under a tight line-height, the overhang of italic letters, and some display fonts' swashes. With line-height: 1 and italic text, our test cut off every descender. Add a little padding where the glyphs overflow; padding enlarges the background area without moving the text much.
.gradient-text {
line-height: 1.1;
padding-block-end: 0.12em; /* room for descenders */
padding-inline: 0.05em; /* room for italic overhang */
}Selection, links, and forced colors
Text selection is the easiest thing to break. Chrome's default highlight draws selected gradient text in a dark color, which stays readable. But if your site styles ::selection with a background and no color, the selected text keeps its transparent color. In our test, selecting the heading showed an empty highlight bar with no visible letters. Always set both:
.gradient-text::selection {
background: #fde68a;
color: #1c1917;
-webkit-text-fill-color: #1c1917; /* if you used the fill property */
}Links need extra thought. Hover, focus, and visited states usually change color, but here the color is transparent. Change the gradient or add an underline for hover, and keep a visible focus outline, which is unaffected by the clipping.
In forced colors mode (for example Windows high contrast themes), browsers replace author colors with system colors. With forced colors emulated in Chrome, our gradient headings rendered as plain text in the system text color, which is the outcome you want. Test there if your audience uses it, especially if you rely on -webkit-text-fill-color.
Contrast at every stop
A gradient has no single contrast ratio. Every letter sits on the page background, and its color is whatever part of the gradient it covers. Check each stop against the background. If the lightest part of the gradient fails, the letters there fail. Two measurements using the WCAG 2 formula:
- On white: #7c3aed measures 5.70:1 and #db2777 4.60:1, but #f59e0b, a common amber end stop, measures only 2.15:1, below even the 3:1 required for large text. Replacing it with #b45309 (5.02:1) keeps a warm end that passes.
- On a near-black #09090c: #7c3aed drops to 3.49:1. That is enough for large text but not for normal text. Lighter stops such as #a78bfa (7.31:1), #f472b6 (7.51:1), and #fbbf24 (11.91:1) work on dark backgrounds.
The same gradient rarely works on both a light and a dark theme. Define the stops as custom properties and swap them per theme. Measure placed over an image or another gradient at the darkest and lightest points behind the text. The Journal's contrast guide covers that case.
:root {
--text-grad: linear-gradient(90deg, #7c3aed, #db2777, #b45309);
}
@media (prefers-color-scheme: dark) {
:root {
--text-grad: linear-gradient(90deg, #a78bfa, #f472b6, #fbbf24);
}
}
.gradient-text { background-image: var(--text-grad); }More on measuring text over changing backgrounds: Contrast, in context. Gradient directions, stop positions, and interpolation in Tailwind: Gradients in Tailwind CSS v4.
Gradient text in Tailwind CSS v4
Combine a gradient utility with bg-clip-text and text-transparent. Add w-fit or inline-block for short headings and a little bottom padding for descenders:
<h1 class="w-fit bg-linear-to-r from-violet-600 via-pink-600 to-amber-700
bg-clip-text pb-1 text-5xl font-semibold text-transparent
selection:bg-amber-200 selection:text-stone-900">
Release notes
</h1>bg-clip-text generates only the unprefixed background-clip: text. Tailwind's production build runs the CSS through Lightning CSS, which adds -webkit-background-clip: text for the browser versions Tailwind targets. If you copy the class's CSS into another project by hand, add the prefix yourself. The selection: variant applies ::selection styles to the element and its descendants. Set a text color there for the reason described above.
For the @supports fallback, a custom utility is easier to read than a long arbitrary supports-[…] variant:
@import "tailwindcss";
@utility text-gradient {
color: var(--color-violet-700);
@supports (background-clip: text) or (-webkit-background-clip: text) {
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
}
/* <h1 class="text-gradient w-fit bg-linear-to-r from-violet-600 to-pink-600"> */When not to use it
- Body text, captions, labels, and anything small. Thin strokes show only a sliver of the gradient, and the color shifts make reading slower.
- Long headings that wrap to three or four lines. The gradient becomes a series of unrelated colored lines.
- Text over photos, video, or another gradient. Two changing layers make contrast impossible to guarantee.
- Buttons and navigation. They need clear hover, focus, active, and disabled states, and those are much harder to show when the text color is transparent.
- Several places on one page. One gradient heading draws attention; five compete with each other and with the real calls to action.
Common mistakes
- Writing color: transparent without the -webkit- clip and without a fallback, which leaves invisible text where the clip is not supported.
- Leaving a block heading at full width so only part of the gradient shows.
- Styling ::selection with a background but no text color.
- Checking contrast only at the gradient's darkest or middle color.
- Animating background-position on large gradient headlines in a loop. It repaints every frame and is distracting; if you animate it at all, stop the animation under prefers-reduced-motion.
Making one with the GradientDeck text gradient generator
The GradientDeck text gradient generator previews the effect on your own text. Type a heading into the preview field, choose a direction and start and end colors from the Tailwind palette, and add a middle color with the switch if you want three stops. G picks a random combination. The CSS output contains the background-image, both background-clip declarations, and color: transparent. The Tailwind output gives the direction, from-, via-, and to- classes with bg-clip-text and text-transparent. Neither output includes a fallback or the width and padding fixes, so add those from this article. The direction class is the v4 form: bg-linear-to-* for linear, and bg-radial-[at_…] or bg-conic-[at_…] for radial and conic. Below the generator, a gallery of ready-made text gradients copies each one as Tailwind classes or plain CSS.