Visual effects

Depth and texture

  • Layer multiple shadows for realistic soft depth effects.
  • Use filter: drop-shadow() instead of box-shadow for non-rectangular shapes or transparent PNGs.
  • Use mix-blend-mode and background-blend-mode for lighting overlays (limit scope with isolation: isolate)
.hero {
  background-image: url('texture.png'), linear-gradient(to bottom, #fff, #eee);
  background-blend-mode: soft-light;
}

Gradients and color-mix()

Use in oklch or in oklab to explicitly specify the interpolation color space for gradients or color-mix().

  • in oklch preserves chroma better, but can more easily get out of device gamut, especially for bigger differences between colors
  • in oklab stays in gamut more easily (assuming in-gamut endpoints) but can create washed out desaturated colors in the middle, especially when interpolating between opposite hues.
  • DON'T use in srgb unless you have a specific reason to do so (e.g. you are building a color picker that needs to interpolate in srgb).

Fallback

Some pre-2024 browsers do not support gradient color interpolation space. To support these browsers, use the token only when its usage is safe by defining a variable:

:root {
  --in-oklab: ;
  --in-oklch: ;
}

@supports (linear-gradient(in oklab, white, black)) {
  :root {
    --in-oklab: in oklab;
    --in-oklch: in oklch;
  }
}

Then use like:

.card {
  background: linear-gradient(to bottom var(--in-oklab), var(--accent-color), var(--darker));
}
  • Important: If you use this technique, make sure there is always a non-empty gradient preamble without it, otherwise it will be a syntax error in older browsers.
  • You do NOT need this for color-mix(). If a browser supports color-mix(), it also supports its in <color-space> argument.

Patterns

Many patterns can be created via CSS gradients + hard stops, and these can be more flexible and performant than SVGs or external images as they can have access to CSS variables and lengths from the surrounding context. You don't need to repeat the position twice — just use 0 or 0% and gradient fixup will auto-adjust it.

Examples below.

Vertical stripes of 1em width each:

background: linear-gradient(to right, var(--color-1) 50%, var(--color-2) 0) 0 / 2em;

Diagonal stripes of 1em width each:

background: repeating-linear-gradient(-45deg, var(--color-1) 0 1em, var(--color-2) 0 2em);

Checkerboard pattern with 1em squares:

background: repeating-conic-gradient(var(--color-1) 0 25%, var(--color-2) 0 50%) 0 / 2em 2em;

Polka dot with .5em radius dots spaced 2em apart (horizontally/vertically — multiply by sqrt(2) for diagonal distance):

--distance: 2em;
--radius: .5em;
--polka: radial-gradient(circle, var(--color-1) var(--radius), transparent calc(var(--radius) + 1px));
background: var(--polka) 0 0, var(--polka) var(--distance) var(--distance) var(--color-2);
background-size: calc(var(--distance) * 2) calc(var(--distance) * 2);

Simple pie chart:

.pie {
  --p: 80%;
  width: 60px;
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(var(--color-1) var(--p), transparent 0%) var(--color-2);
}

Important: When using gradients to render charts, ensure there is a textual fallback for screen readers. MANDATORY: You MUST provide a semantic data table as an accessible alternative, as detailed in Accessibility Coding Guidelines under the alternate text and media guidelines.