DEV Community

Michael Lip
Michael Lip

Posted on • Originally published at zovo.one

CSS Gradients: Why Your Color Transitions Look Muddy (and How to Fix Them)

You define a gradient from a nice blue to a nice yellow, expecting a clean, vibrant transition. Instead, the middle of the gradient is a muddy gray-brown. You've seen this. Every developer who works with CSS gradients has seen this. And the reason it happens is a fundamental issue with how browsers interpolate color.

/* This produces a muddy middle */
background: linear-gradient(to right, #3498db, #f1c40f);
Enter fullscreen mode Exit fullscreen mode

The default interpolation happens in sRGB color space, which means the browser takes the red, green, and blue channels of each endpoint and linearly transitions between them. When you go from blue (low red, moderate green, high blue) to yellow (high red, high green, low blue), the midpoint has moderate values across all three channels. That's gray. More precisely, it's a desaturated brownish gray that doesn't belong in any design system.

The color space fix

CSS now lets you specify which color space to use for interpolation:

/* sRGB - the default, often muddy */
background: linear-gradient(in srgb to right, #3498db, #f1c40f);

/* OKLCH - perceptually smooth, stays vibrant */
background: linear-gradient(in oklch to right, #3498db, #f1c40f);

/* HSL - better than sRGB, but not perceptually uniform */
background: linear-gradient(in hsl to right, #3498db, #f1c40f);
Enter fullscreen mode Exit fullscreen mode

The in oklch option makes the biggest difference. OKLCH interpolation follows the perceptual color wheel, so a gradient from blue to yellow passes through cyan and green instead of through gray. The transition stays saturated the entire way.

Browser support for in oklch landed in Chrome 111, Firefox 127, and Safari 16.4. If you're building for modern browsers, this is the single best improvement you can make to your gradients.

Linear, radial, and conic

CSS supports three gradient types, each with its own geometry:

/* Linear: color transition along a line */
background: linear-gradient(135deg, #667eea, #764ba2);

/* Radial: color transition from a center point outward */
background: radial-gradient(circle at 30% 40%, #667eea, #764ba2);

/* Conic: color transition around a center point */
background: conic-gradient(from 0deg at 50% 50%, #667eea, #764ba2, #667eea);
Enter fullscreen mode Exit fullscreen mode

Linear gradients are the workhorse. The angle parameter (or direction keyword like to right, to bottom left) controls the line along which colors change. Radial gradients are useful for spotlight effects and vignettes. Conic gradients are underused -- they're great for pie charts, color wheels, and decorative patterns.

Hard stops create flat stripes

You can create sharp color boundaries instead of smooth transitions by placing two color stops at the same position:

/* Smooth gradient */
background: linear-gradient(to right, #3498db, #e74c3c);

/* Hard stop at 50% */
background: linear-gradient(to right, #3498db 50%, #e74c3c 50%);

/* Multiple stripes */
background: linear-gradient(
  to right,
  #3498db 0%, #3498db 33%,
  #e74c3c 33%, #e74c3c 66%,
  #f1c40f 66%, #f1c40f 100%
);
Enter fullscreen mode Exit fullscreen mode

This technique replaces what developers often use images for. Striped progress bars, flag-like patterns, and segmented backgrounds can all be done with pure CSS gradients.

Repeating gradients for patterns

repeating-linear-gradient and repeating-radial-gradient tile the gradient pattern:

/* Diagonal stripes */
background: repeating-linear-gradient(
  45deg,
  #f0f0f0,
  #f0f0f0 10px,
  #ffffff 10px,
  #ffffff 20px
);

/* Concentric circles */
background: repeating-radial-gradient(
  circle,
  #f0f0f0 0px,
  #f0f0f0 10px,
  #ffffff 10px,
  #ffffff 20px
);
Enter fullscreen mode Exit fullscreen mode

These are useful for backgrounds, loading states, and placeholder patterns. The key insight is that the color stops use fixed units (px, rem) instead of percentages, and the browser repeats the pattern to fill the element.

Common gradient mistakes

1. Too many color stops. Three to four colors is usually the sweet spot. More than that and the gradient becomes noisy and hard to read. If you need many colors, consider whether a single solid color with a subtle gradient overlay would be more effective.

2. Ignoring text readability. A gradient background under text almost always has a region where contrast is insufficient. If you must put text on a gradient, add a semi-transparent overlay:

.hero {
  background: linear-gradient(135deg, #667eea, #764ba2);
  position: relative;
}

.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.3);
}
Enter fullscreen mode Exit fullscreen mode

3. Not using the angle shorthand. to right is 90deg. to bottom is 180deg (the default). to bottom right is 135deg. Using degree values gives you finer control, but the keyword forms are more readable for standard directions.

4. Forgetting the fallback. For browsers that don't support your gradient syntax, always declare a solid background-color before the gradient:

.element {
  background-color: #667eea;
  background: linear-gradient(in oklch to right, #667eea, #764ba2);
}
Enter fullscreen mode Exit fullscreen mode

5. Performance with large elements. CSS gradients are painted by the browser's rasterizer on every repaint. On very large elements or with complex gradients (many stops, radial), this can cause jank during scrolling. A static gradient image might perform better in extreme cases. In practice, this rarely matters for typical UI elements, but it's worth knowing for full-screen backgrounds.

Gradient as design system primitive

In a design system, gradients should be tokenized just like colors:

:root {
  --gradient-primary: linear-gradient(in oklch 135deg, var(--color-primary-500), var(--color-primary-700));
  --gradient-accent: linear-gradient(in oklch 135deg, var(--color-accent-400), var(--color-accent-600));
  --gradient-surface: linear-gradient(180deg, var(--color-surface), var(--color-surface-raised));
}
Enter fullscreen mode Exit fullscreen mode

This keeps gradient definitions consistent and maintainable. When the brand colors change, the gradients update automatically.

For experimenting with gradient combinations without writing CSS by hand, I built a gradient generator at zovo.one/free-tools/gradient-generator that lets you adjust stops, angles, and color spaces visually and gives you the CSS to copy.

Gradients are one of the most powerful visual tools in CSS. The difference between a muddy gradient and a polished one is almost always the color space. Switch to OKLCH interpolation, keep your stop count low, and test readability over every region of the gradient. Three rules for better visuals everywhere.


I'm Michael Lip. I build free developer tools at zovo.one. 350+ tools, all private, all free.

Top comments (0)