Tailwind CSS v4 replaced the single bg-gradient-to-* family with three shape utilities: bg-linear-*, bg-radial-*, and bg-conic-*. The from-*, via-*, and to-* stop utilities still exist, but the CSS they generate changed, and a few v3 patterns now render nothing at all. The examples below were compiled with Tailwind CSS 4.3 and checked in Chrome.
How the classes fit together
Every gradient is built from two custom properties. The shape utility writes --tw-gradient-position (the direction, angle, or radial position, plus an interpolation space) and sets background-image. The stop utilities assemble --tw-gradient-stops, which begins with the position and then lists the colors. Simplified, the output for bg-linear-to-r from-indigo-500 to-pink-500 looks like this:
.bg-linear-to-r {
--tw-gradient-position: to right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.from-indigo-500 {
--tw-gradient-from: var(--color-indigo-500);
--tw-gradient-stops: var(--tw-gradient-position),
var(--tw-gradient-from) var(--tw-gradient-from-position),
var(--tw-gradient-to) var(--tw-gradient-to-position);
}Two consequences follow. A stop class without a shape class produces no gradient, because nothing sets background-image. And the position is part of the stop list, which matters when you write gradients by hand in arbitrary values (covered under migration below).
Linear gradients: directions and angles
Use the eight keyword directions (bg-linear-to-t, -tr, -r, -br, -b, -bl, -l, -tl) or a number of degrees. Any whole number works, not only the values your editor suggests, and a leading dash negates the angle.
<div class="bg-linear-to-br from-sky-400 to-indigo-600"></div>
<div class="bg-linear-65 from-purple-500 to-pink-500"></div>
<div class="-bg-linear-45 from-amber-300 to-rose-500"></div>Corner keywords and angles behave differently on non-square boxes. With to top right, the browser computes the angle from the element's aspect ratio so the midpoint line runs between the other two corners; on a wide banner the color change ends up running almost top to bottom. An angle such as bg-linear-45 stays at 45 degrees whatever the shape. Use a keyword when the gradient should follow the box, and an angle when several elements of different sizes need to match.
Color stops and positions
from-*, via-*, and to-* accept any theme color, an opacity modifier, an arbitrary value, or a CSS variable in parentheses. Stop positions are separate classes with a percentage. Without them, the stops sit at 0%, 50%, and 100%.
<!-- Positions: hold indigo until 10%, sky at 30%, emerald from 90% -->
<div class="bg-linear-to-r from-indigo-500 from-10% via-sky-500 via-30% to-emerald-500 to-90%"></div>
<!-- Opacity modifier, arbitrary color, and a CSS variable -->
<div class="bg-linear-90 from-(--brand) via-[#f472b6] to-blue-500/50"></div>
<!-- A fade for an image caption -->
<div class="bg-linear-to-t from-black/70 to-transparent"></div>Moving stop positions is usually more useful than adding colors. A hero that is mostly one color with a short transition at the edge reads calmer than an even three-color sweep: from-slate-900 from-60% to-indigo-800 keeps the text area flat and pushes the color change to the end.
Gradients and variants
In v3, changing one stop in a variant reset the whole gradient, so dark:from-blue-500 left the to-* color transparent in dark mode. In v4 the other stops are preserved. If a state should drop the middle color, say so with via-none:
<div class="bg-linear-to-r from-red-500 via-orange-400 to-yellow-400
dark:via-none dark:from-blue-500 dark:to-teal-400"></div>Radial and conic gradients
bg-radial draws an ellipse from the center. To move it or change its shape, pass the CSS radial-gradient prelude as an arbitrary value, with underscores for spaces. Conic gradients take an optional start angle (bg-conic-180) or a full prelude in brackets.
<!-- Radial: centered, offset, and a circle that fades out at 70% -->
<div class="bg-radial from-pink-400 from-40% to-fuchsia-700"></div>
<div class="bg-radial-[at_25%_25%] from-white to-zinc-900 to-75%"></div>
<div class="bg-radial-[circle_at_center] from-white/40 to-transparent to-70%"></div>
<!-- Conic: repeat the first color at the end so there is no seam -->
<div class="size-24 rounded-full bg-conic from-red-500 via-yellow-400 to-red-500"></div>
<div class="size-24 rounded-full bg-conic-[from_90deg_at_top] from-indigo-600 to-sky-300"></div>Conic gradients have a hard edge where the last color meets the first. Ending on the starting color removes it, which is what color wheels and spinner rings need. For a loading ring, combine the conic background with a mask or an inner circle rather than drawing it with borders.
Interpolation modifiers
Tailwind v4 writes in oklab into the gradient position by default (inside an @supports check, so older browsers still get a gradient). That differs from plain CSS. The CSS Color 4 specification says gradients between legacy colors such as hex, rgb(), and hsl() interpolate in gamma-encoded sRGB unless you specify a space. So the same two hex values can look different in a v3 project, a hand-written gradient, and a v4 utility.
A slash modifier picks the space: /srgb, /hsl, /oklab, or /oklch. For polar spaces, /shorter, /longer, /increasing, and /decreasing choose which way round the hue wheel to travel; on their own they use oklch. An arbitrary modifier accepts any space the browser supports.
<div class="bg-linear-to-r/srgb from-blue-500 to-yellow-400"></div>
<div class="bg-linear-to-r from-blue-500 to-yellow-400"></div> <!-- oklab -->
<div class="bg-linear-to-r/oklch from-blue-500 to-yellow-400"></div>
<div class="bg-linear-to-r/longer from-blue-500 to-yellow-400"></div> <!-- oklch, long way round -->
<div class="bg-linear-to-r/[in_lab] from-blue-500 to-yellow-400"></div>The difference shows most between hues on opposite sides of the wheel. We mixed blue-500 and yellow-400 at 50% in Chrome. sRGB gives a grayish olive (about rgb(155 167 133)). Oklab gives a lighter, still muted gray-teal (about rgb(157 176 176)). Oklch keeps the chroma and passes through a saturated teal-green, and /longer goes the other way, through pink. Neither is always right. Oklab avoids the dark, muddy middle that sRGB produces. Oklch keeps the colors vivid but can add a hue that was not in either stop. Its midpoint may also fall outside sRGB, and the browser then maps it back to a displayable color.
Arbitrary values and CSS variables
When the stop utilities are not enough (more than three colors, hard stops, or colors from a CMS), pass the whole gradient argument list. bg-linear-[...] and bg-linear-(--var) use it as the fallback for --tw-gradient-stops, so they work on their own as long as the element has no from-*, via-*, or to-* class.
<!-- Four stops with a hard edge at 60% -->
<div class="bg-linear-[25deg,red_5%,yellow_60%,lime_60%,teal]"></div>
<!-- Gradient arguments from a variable, set inline or in CSS -->
<div class="bg-linear-(--promo-gradient)"
style="--promo-gradient: 135deg, #0f172a, #4338ca 70%, #db2777"></div>
<!-- A complete background-image value, layered gradients included -->
<div class="bg-(image:--hero-bg)"></div>The image: type hint in bg-(image:--hero-bg) tells Tailwind the variable holds an image, not a color. Without it, bg-(--hero-bg) is treated as a background color.
Migrating from bg-gradient-to-*
The official upgrade tool (npx @tailwindcss/upgrade) rewrites bg-gradient-to-r to bg-linear-to-r, and so on for all eight directions. The old class names still compile in 4.3, but they now include in oklab as well, so renaming them is not the only visual change. Check these three things after an upgrade:
- Arbitrary gradients that reference var(--tw-gradient-stops). In v3 it was common to write bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] for a radial gradient. In v4 --tw-gradient-stops starts with the position variable, and with no shape utility that variable is unset, so the whole background-image is invalid. In our Chrome test the element rendered with background-image: none. Replace these with bg-radial-[ellipse_at_top] or bg-conic-[at_top] and keep the from/via/to classes.
- Gradients built from hex or rgb colors. They now interpolate in Oklab instead of sRGB, so midpoints shift, usually lighter and less gray. Add /srgb where an exact match with the old design matters.
- Variants that relied on the reset behavior. If dark:from-* used to give you a two-stop gradient by accident, add dark:via-none.
<!-- v3 -->
<div class="bg-gradient-to-r from-cyan-500 to-blue-500"></div>
<div class="bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-sky-400 to-transparent"></div>
<!-- v4 -->
<div class="bg-linear-to-r from-cyan-500 to-blue-500"></div>
<div class="bg-radial-[ellipse_at_top] from-sky-400 to-transparent"></div>Common mistakes
- Building class names from strings at runtime, such as joining "from-" + color + "-500" in JavaScript. Tailwind only generates classes it finds written out in your source. Map values to complete class names, or pass the color through a CSS variable and use from-(--stop).
- Expecting a gradient on text without clipping it. A gradient is a background image; for text you also need bg-clip-text and text-transparent.
- Putting a gradient behind body text without checking the lightest and darkest areas. Measure contrast at both ends of the gradient, not at its average color.
- Animating background-position on a large gradient to make it shift. It repaints on every frame. If you need the movement, animate the transform or opacity of a gradient layer instead, and stop it for prefers-reduced-motion.
Building one in the GradientDeck generator
The GradientDeck gradient generator sets a direction and a start, optional middle, and end color, then copies the result as CSS, Tailwind classes, or an AI prompt. The Tailwind output uses the v4 direction classes (bg-linear-to-r, bg-radial-[at_top], bg-conic-[at_top_right] and so on) with from-*, via-*, and to-* classes. Tailwind palette colors stay as tokens like from-blue-500, and custom colors become arbitrary values like from-[#3b82f6]. The CSS output is a single background declaration with explicit 0%, 50%, and 100% stops, which you can paste into a bg-linear-[...] value or edit to add positions and an interpolation space.
References
Tailwind CSS: background-image (gradients)
W3C: CSS Color Module Level 4, color space for interpolation