developer · · Updated

10 CSS Gradient Ideas You Can Copy and Customize

By

Each gradient below is CSS you can paste and adjust. After the list are the details that trip people up most: which way the angle points, which browser versions support each technique, and contrast for text on top.

1. Primary button

.btn-primary {
  background: linear-gradient(135deg, #4f46e5, #0e7490);
  color: #ffffff;
}

Indigo to a deep cyan on a 135° diagonal. The deep cyan is deliberate. The brighter #06b6d4 that many templates use looks nicer, but white text on it only reaches a 2.43:1 contrast ratio. With #0e7490, both ends pass for white text (6.29:1 and 5.36:1).

2. Warm call-to-action banner

.promo {
  background: linear-gradient(90deg, #f97316, #db2777);
}

Orange to pink, running left to right. A horizontal flow suits wide, short elements such as banners.

3. Dark dashboard surface

.panel {
  background: linear-gradient(180deg, #111827, #1f2937);
}

Two neighbouring greys, top to bottom. It's barely visible, which is the point: it adds depth without competing with the charts on top.

4. Pastel section background

.section-soft {
  background: linear-gradient(120deg, #eef2ff 0%, #ecfeff 100%);
}

Both ends are nearly white, so dark body text passes contrast checks everywhere. Use it to separate page sections without a hard border.

5. Progress bar with a hard stop

.progress {
  background: linear-gradient(90deg, #0ea5e9 65%, #e2e8f0 65%);
}

When two colour stops share a position, there's no transition between them, just a sharp edge. This one draws a 65%-full bar with no extra element. To drive it from data, set the percentage with a custom property:

<div class="progress" role="progressbar" aria-label="Upload progress"
     aria-valuenow="65" style="--done: 65%"></div>
.progress {
  background: linear-gradient(90deg, #0ea5e9 var(--done), #e2e8f0 var(--done));
}

A screen reader can't see a background, so the attributes do the announcing. WAI-ARIA gives role="progressbar" a range of 0 to 100 unless you set aria-valuemin and aria-valuemax, and requires a name, here from aria-label. Update aria-valuenow whenever --done changes. A native <progress value="65" max="100"> element is announced without any ARIA, but its bar is styled through browser-specific pseudo-elements, which makes a gradient fill harder.

6. Diagonal warning stripes

.maintenance-strip {
  background: repeating-linear-gradient(45deg, #f59e0b 0 12px, #111827 12px 24px);
}

repeating-linear-gradient tiles its stops. Each colour here uses two positions (start and end), so the stripes have hard edges: amber from 0 to 12px, then near-black from 12px to 24px, repeated.

7. Spotlight glow behind a heading

.hero {
  background:
    radial-gradient(circle at 50% 0%, rgb(56 189 248 / 0.35), transparent 60%),
    #0f172a;
}

A soft circle of light at the top centre, fading out by 60% of the gradient's size, over a solid navy base. The navy at the end of the list is the base colour: it's painted under the gradient and shows wherever the gradient is transparent. It isn't a fallback. A browser that rejected the gradient would drop the whole background declaration, colour included. A real fallback is a separate declaration before it, the pattern #10 uses.

8. Gradient text

.headline {
  color: #7c3aed;
}
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .headline {
    background: linear-gradient(90deg, #7c3aed, #db2777);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

The gradient is painted as the element's background, then clipped to the shape of the letters. Keep the -webkit- line: according to MDN's compatibility data, Chrome and Edge accepted the text value only in the prefixed property until version 120 (December 2023), and Safari until version 14.

The @supports block is cheap insurance. Without it, a browser that can't clip to text still applies color: transparent, and the heading becomes a coloured block with no visible letters. With it, that browser shows solid purple text instead. Use gradient text for large headings only: thin body text in a gradient is hard to read.

9. Caption overlay on a photo

.card-photo {
  background:
    linear-gradient(to bottom, rgb(0 0 0 / 0) 40%, rgb(0 0 0 / 0.75)),
    url("team.jpg") center / cover;
}

The top 40% of the photo stays untouched, then darkens towards the bottom. White caption text becomes readable whatever the photo looks like.

10. Blue to yellow without a grey middle

.brand-strip {
  background: linear-gradient(90deg, #2563eb, #22c55e, #facc15);
  background: linear-gradient(90deg in oklch, #2563eb, #facc15);
}

When every stop is a hex code, a colour name, rgb(), hsl() or hwb(), browsers blend the gradient in sRGB, which CSS Color 4 requires for those older formats. If any stop uses a newer syntax such as oklch(), the default is Oklab instead (CSS Images 4). For opposite colours the sRGB halfway point is dull: pure blue #0000ff to pure yellow #ffff00 passes through flat grey #808080 in the middle.

Adding in oklch blends through OKLCH, a perceptual colour space, which keeps the middle vivid. Here is the middle pixel of each version, rendered 401 px wide in Chromium 153, Firefox 153 and WebKit 26.5 (the three engines agreed to within 1 per channel):

Middle of #2563eb → #facc15ColourOKLCH chroma
Default sRGB blendrgb(144 152 128), a grey-green0.036
in oklchrgb(0 196 158), a teal0.141

Chroma is OKLCH's measure of colourfulness: the two ends are 0.215 and 0.173, so the sRGB middle has lost almost all of it. The exact OKLCH halfway colour, oklch(0.703 0.194 177), is slightly outside sRGB (its red channel would be negative), so the browsers clipped it, which is why the measured chroma is 0.141 rather than 0.194. The first declaration, with a green stop added by hand, is a fallback: browsers that don't understand the in oklch line keep using it.

Which way does the angle point?

  • 0deg runs bottom to top, 90deg left to right, and 180deg top to bottom. The angle is the direction the gradient travels, measured clockwise from straight up (CSS Images 3).
  • to top right matches 45deg only on a square. The corner keyword tilts the gradient so that its halfway colour runs exactly through the other two corners, top-left and bottom-right. On a 400×100 px banner that works out to about 14deg. 45deg stays at 45° whatever the shape, so on the same banner those two corners get the 20% and 80% colours instead. Either way, the first colour lands exactly on the bottom-left corner and the last on the top-right.

Browser support

These are the first versions that support each technique as written above, according to MDN's browser-compat-data (October 2026). Current versions of all four browsers support every one of them.

TechniqueChromeEdgeFirefoxSafari
linear-gradient(), radial-gradient() and hard stops (#1–#5, #7, #9)2612167
Custom property in a stop, var(--done) (#5)4915319.1
Two positions on one stop, #f59e0b 0 12px (#6)71796412.1
Space-separated rgb() with / alpha (#7, #9)65795212.1
background-clip: text without the prefix (#8)1201204914
in oklch interpolation (#10)11111112716.2

Contrast check before you ship

Text over a gradient has to pass contrast at both ends, not just in the middle. WCAG 2.2 success criterion 1.4.3 (level AA) asks for a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text, which WCAG defines as at least 18 pt (24 px), or 14 pt (about 18.7 px) bold. The ratios below come from WCAG's relative luminance formula, using the colours above:

Gradient endWhite textDark text (#111827)
#4f46e5 (indigo)6.29:12.82:1
#0e7490 (deep cyan)5.36:13.31:1
#f97316 (orange)2.80:16.33:1
#db2777 (pink)4.60:13.86:1

The orange-to-pink banner (#2) is the trap: white text fails at the orange end, and dark text is too weak at the pink end for body copy. Use large dark text there, or darken the orange stop until white text passes.

Keep stops in variables

If your brand colours show up in several gradients, define them once:

:root {
  --brand-a: #4f46e5;
  --brand-b: #0e7490;
}
.btn-primary { background: linear-gradient(135deg, var(--brand-a), var(--brand-b)); }

A dark theme then only needs to redefine --brand-a and --brand-b.

Building your own

The Color Picker & Gradient Generator makes two-colour linear gradients like #1–#3: pick both colours, drag the angle, and copy the CSS. It also converts each colour between HEX, RGB and HSL. For three to six stops, stop positions (#4 and #5), or a centred radial gradient, use the CSS Gradient Generator. Off-centre radial positions like #7, the repeating- variants, and in oklch are quick hand edits to the copied code. If you're choosing the colours themselves, the HEX, RGB and HSL guide explains why HSL makes lighter and darker shades easy.

Frequently Asked Questions

Why does my CSS gradient look grey in the middle?

Gradients between hex, rgb() or hsl() colours are blended in sRGB, and the straight line between two opposite hues passes close to grey. Blue #2563eb to yellow #facc15 measured rgb(144 152 128) at its midpoint in Chromium, Firefox and WebKit, an OKLCH chroma of 0.036. Writing the gradient with "in oklch" gave a teal midpoint with chroma 0.141. Browsers without that feature drop the line, so put a version with a hand-added middle stop before it.

Is "to top right" the same as 45deg?

Only on a square. The corner keyword angles the gradient so its halfway colour runs through the top-left and bottom-right corners, so on a 400×100 px element it works out to about 14deg. An angle stays fixed whatever the element's shape. In both cases the first colour sits on the bottom-left corner and the last on the top-right.

Does gradient text still need -webkit-background-clip?

Keep it alongside the unprefixed line. Chrome and Edge accepted background-clip: text only with the -webkit- prefix until version 120 (December 2023), and Safari until version 14. Current versions of every major browser accept the unprefixed property.

Is checking both ends of a gradient enough for text contrast?

For white text on an sRGB-blended gradient, yes: no point in between is lighter than the lightest stop. For dark text, no, because the middle can be darker than both ends. #111827 text on a gradient from #ef4444 to #3b82f6 passes 4.5:1 at both ends (4.71:1 and 4.82:1) but drops to about 3.8:1 just before the middle. Check the darkest point too, or add a lighter middle stop.

Related guides