Blue gradients in CSS: palettes, hex codes and blends that stay blue
Blue gradient palettes with hex codes, why blue drifts purple or cyan as it fades, and the CSS fix.
Updated
A blue gradient runs from a pale, almost white blue through a bright mid blue to navy. In CSS it's one linear-gradient. Add in oklab so the blue stays blue as it lightens, instead of drifting toward lavender:
.blue {
background: linear-gradient(to bottom, #e8f2ff, #7fb2ff, #2457e6, #081a4a);
background: linear-gradient(
to bottom in oklab,
#e8f2ff,
#7fb2ff,
#2457e6,
#081a4a
);
}Those are the four colours of Flood, one of the free presets. The first line is the fallback for browsers that don't support in oklab. The demo shows Flood, the six other blue palettes from the table below, and pure blue fading to white three ways. Everything in it is plain CSS.
import type { ReactNode } from "react";
/**
* The blue recipes from the blue gradient blog post, all plain CSS. The
* palettes are the colours of seven free presets, lightest to darkest.
*/
const palettes: readonly { name: string; colors: readonly string[] }[] = [
{ name: "Flood", colors: ["#e8f2ff", "#7fb2ff", "#2457e6", "#081a4a"] },
{ name: "Film Blue", colors: ["#d9f1ff", "#5fb4e6", "#2b5fbe", "#14204a"] },
{ name: "Earthshine", colors: ["#e8f4ff", "#4aa3ff", "#1d4fd6", "#06143a"] },
{ name: "Europa", colors: ["#f0fbff", "#a6e4ff", "#3d9bd6", "#0a2a40"] },
{ name: "Blue Hour", colors: ["#eef8ff", "#8fd0f5", "#3d6fd0", "#1b1846"] },
{ name: "Cobalt Rise", colors: ["#f4f6ff", "#9fbcff", "#2e5bff", "#14208f"] },
{ name: "Projector", colors: ["#f4f7ff", "#8fb4ff", "#3d5bff", "#1a1660"] },
];
const blends = ["srgb", "oklab", "oklch"] as const;
const flood = palettes[0].colors.join(", ");
const label = "relative text-sm font-semibold text-white";
/**
* A box painted with `modern`, over `fallback` for browsers that drop
* gradients with an interpolation space, so no swatch ever renders empty.
*/
function Swatch({
className,
fallback,
modern,
children,
}: {
className: string;
fallback: string;
modern: string;
children: ReactNode;
}) {
return (
<div
className={`relative overflow-hidden ${className}`}
style={{ background: fallback }}
>
<div
aria-hidden
className="absolute inset-0"
style={{ background: modern }}
/>
{children}
</div>
);
}
export function BlueGradientsDemo() {
return (
<div className="grid w-full gap-3 bg-[#01030b] p-4">
<Swatch
className="flex h-48 items-end rounded-lg p-3"
fallback={`linear-gradient(to bottom, ${flood})`}
modern={`linear-gradient(to bottom in oklab, ${flood})`}
>
<span className={label}>Flood, light to dark, in oklab</span>
</Swatch>
<div className="grid gap-3 sm:grid-cols-2">
{palettes.map((palette) => (
<Swatch
key={palette.name}
className="flex h-20 items-start justify-end rounded-lg p-3"
fallback={`linear-gradient(to right, ${palette.colors.join(", ")})`}
modern={`linear-gradient(to right in oklab, ${palette.colors.join(", ")})`}
>
<span className={label}>{palette.name}</span>
</Swatch>
))}
</div>
<div className="grid gap-2">
{blends.map((space) => (
<Swatch
key={space}
className="flex h-10 items-center rounded-md px-3"
fallback="linear-gradient(to right, #0000ff, #ffffff)"
modern={`linear-gradient(to right in ${space}, #0000ff, #ffffff)`}
>
<span className={label}>in {space}</span>
</Swatch>
))}
</div>
</div>
);
}
Blue palettes with hex codes
These are the colours of seven free presets, lightest to darkest. Each row works as a four-stop gradient on its own.
| Preset | Light | Mid-light | Mid-dark | Dark |
|---|---|---|---|---|
| Flood | #e8f2ff | #7fb2ff | #2457e6 | #081a4a |
| Film Blue | #d9f1ff | #5fb4e6 | #2b5fbe | #14204a |
| Earthshine | #e8f4ff | #4aa3ff | #1d4fd6 | #06143a |
| Europa | #f0fbff | #a6e4ff | #3d9bd6 | #0a2a40 |
| Blue Hour | #eef8ff | #8fd0f5 | #3d6fd0 | #1b1846 |
| Cobalt Rise | #f4f6ff | #9fbcff | #2e5bff | #14208f |
| Projector | #f4f7ff | #8fb4ff | #3d5bff | #1a1660 |
They aren't all the same blue. Measured as OKLCH hue, Europa sits between 221° and 243°, so it leans toward cyan from end to end: an icy, watery blue. Film Blue and Blue Hour start cyan at the light end and turn to royal blue lower down. Blue Hour ends at 281°, a deep indigo, and Projector's darkest stop is close behind at 276°. Flood and Cobalt Rise hold a true blue from top to bottom, between 255° and 276°. Earthshine starts a little toward cyan, at 246°, and settles at 265°.
Why blue turns purple as it fades
A plain linear-gradient blends in sRGB, which mixes the red, green and blue channels in straight lines. For blue fading to white, that is the wrong path. Rendered in Chrome 154, the halfway point between #0000ff and #ffffff is #7f7fff, a periwinkle with an OKLCH hue of 280°, against 264° for the blue you started with. Your eye sees that 16° as a lavender cast in the middle of the gradient. The gradient doesn't fade blue out. It fades it through a purple.
in oklab blends in a space built so that equal steps look equal. Its halfway colour is #73a2ff, at hue 263°, the same blue only lighter.
in oklch, the usual fix for muddy gradients, should give the same result here. White has no hue of its own, and the CSS spec says a gradient to white keeps the other colour's hue. Chrome 154 doesn't do that yet: its halfway colour comes out as #4babff, about 16° toward cyan. The three bars at the bottom of the demo show all three. Starting from Flood's #2457e6 instead of pure blue, the shift is smaller but the pattern is the same: sRGB lands at #91abf2, 4° toward purple; OKLab at #8daff8, within a degree; OKLCH, in Chrome 154, at #77b6f4, 15° toward cyan.
OKLab gets it right in every browser that supports it, because it has no hue to carry. So, for blues:
- Fading blue to white or to a pale blue: use
in oklab. - Blending blue with a far-off hue, like orange or yellow: use
in oklch. Blue#2457e6to orange#f97316in sRGB passes through#8e657e, a grey mauve with an OKLCH chroma of 0.063. OKLab is no better at#96769c(0.067). OKLCH goes around the colour wheel through magenta, and its halfway colour is#ca45b1, with a chroma of 0.205. Sunset gradients in CSS explains the same fix for orange into indigo. - Blue to a vivid pink or violet: any space works. The hues are close enough that the middle stays coloured. Flood's
#2457e6to pink#ec4899has halfway colours with chroma between 0.15 and 0.22 in all three spaces.
in oklab works in Chrome and Edge 111, Safari 16.2 and Firefox 127. Older browsers drop the whole declaration, which is why the example at the top writes a plain gradient first.
Text on a blue gradient
Blue is darker than it looks. Pure #0000ff takes white text at 8.59:1, well past the 4.5:1 that WCAG asks for body text, but the pale end of a blue gradient doesn't. On the light and mid-light stops in the table, white text manages only 1.05:1 to 2.63:1.
- White text over the darker half. White reaches 4.5:1 on every mid-dark and dark stop in the table, except Europa's
#3d9bd6at 3.06:1. Run the gradient light at the top and dark at the bottom, and put the text low. - Dark text over the lighter half.
#0f172a, a near-black navy, reaches at least 6.78:1 on every light and mid-light stop.
Neither colour works across the whole of a light-to-navy gradient. If a heading has to cross the middle, give it a dark scrim or keep the gradient in one half of the range.
Dark blue gradients band
Navy gradients show bands more than most. Flood's darkest stop, #081a4a, against its background, #01030b, differs by 7 steps of 256 in red, 23 in green and 63 in blue. As a rough estimate, a plain 8-bit sRGB gradient between those two colours across a 1440px hero changes its blue channel about once every 23 pixels, and its red channel only seven times in the whole width. In dark colours your eye can pick out those edges as stripes. The shader version adds grain, which hides most of them.
Grain or dither breaks them up. How to add film grain to a gradient adds an SVG noise layer in plain CSS. Ordered dithering in a shader covers the fix inside a shader.
Blue gradients in Tailwind
Tailwind v4 already blends in OKLab. In Tailwind 4.3.3, bg-linear-to-b compiles to to bottom, then to bottom in oklab inside an @supports check. So a blue gradient that stays blue is one class list:
<div class="bg-linear-to-b from-sky-300 via-blue-600 to-blue-950"></div>Add /oklch (as in bg-linear-to-b/oklch) only when the stops are far apart in hue, for the reasons above. In 4.3.3, blue-600 is oklch(54.6% 0.245 262.881), which is #155dfc and takes white text at 5.25:1. Some of Tailwind's brighter blues, including blue-400, blue-500 and sky-300 to sky-500, sit outside sRGB. They are wide-gamut colours: a P3 display shows them as written, and an sRGB screen shows the nearest colour it can, so their hex equivalents are approximations. For hex stops from the table, use arbitrary values such as from-[#e8f2ff].
A blue gradient that moves
A CSS gradient holds still unless you animate it. Flood is the same four colours drawn by a shader: a wash of blue light streaming in from the edge of a near-black frame. It's one of the free presets in the Beams collection:
Flood, live
import { GradientBackground } from "@/components/gradient-surface";
import { kindDefaults, mergeParams } from "@/lib/gradient-engine";
// The free Flood preset: the beam shader, a wash of blue light streaming in
// from the edge of a near-black frame.
const flood = mergeParams(kindDefaults("beam"), {
background: "#01030b",
colors: ["#e8f2ff", "#7fb2ff", "#2457e6", "#081a4a"],
loopSeconds: 12,
seed: 5,
speed: 0.79,
});
export function FloodBeamDemo() {
return (
<GradientBackground
defaults={flood}
className="flex h-[360px] items-start p-8"
>
<h2 className="text-3xl font-semibold tracking-tight text-white">
Flood, live
</h2>
</GradientBackground>
);
}
It draws with WebGPU where the browser supports it and WebGL2 otherwise, loops seamlessly every 12 seconds, and shows one still frame to visitors who prefer reduced motion.
Flood installs with the shadcn CLI and no account, into any React project with a components.json:
npx shadcn@latest add https://gradients.sh/r/c/eyJ2IjoxLCJwcmVzZXRJZCI6ImZsb29kIiwibmFtZSI6IkZsb29kIiwicCI6eyJraW5kIjoiYmVhbSIsImJhY2tncm91bmQiOiIjMDEwMzBiIiwiY29sb3JzIjpbIiNlOGYyZmYiLCIjN2ZiMmZmIiwiIzI0NTdlNiIsIiMwODFhNGEiXSwibG9vcFNlY29uZHMiOjEyLCJzZWVkIjo1LCJzcGVlZCI6MC43OX19.jsonimport { Flood } from "@/components/gradients/flood";
export function Hero() {
return (
<section className="relative isolate min-h-[560px]">
<Flood className="absolute inset-0 -z-10" />
{/* Your content sits on top. */}
</section>
);
}The other palettes in the table are free presets too, each a different shader: Film Blue (heavy film grain), Earthshine and Europa (the lit edge of a planet), Blue Hour (a radial glow), Cobalt Rise (dithered light) and Projector (a beam). Two more put blue on a pale ground: Glasswork, blue and violet light on #eef0f6, and Paper Crease, soft blue folds on warm paper. Open any of them to tune the colours. A free account exports PNGs up to 1920px wide and 10-second MP4 loops up to 1080p60. Flood's loop is 12 seconds, so set its Loop to 10 in the editor before a free MP4 export, or the clip stops before the loop closes.
Related
- Purple gradients in CSS picks up where blue turns violet, with seven palettes and how to blend red and blue into a vivid purple.
- Pastel gradients in CSS covers the pale blues, with the OKLCH range that makes a colour pastel.
- Gradients that follow light and dark mode switches a palette like these with CSS variables.
- Metallic gradients in CSS places stops where light falls, for chrome and gold.
- Green gradients in CSS covers blue's neighbour, from mint and emerald to teal, and why green rarely carries white text.