On this page

Colors and backgrounds in CSS

10 min read TextCh. 4 — Visual Styles

Color systems in CSS

CSS offers multiple ways to define colors. Each one has its advantages depending on the use case.

Traditional formats

Format Example Typical use
Name red, blue Quick prototyping
Hexadecimal #ff5500 Copy from Figma/Photoshop
RGB rgb(255 85 0) When you need transparency
HSL hsl(20 100% 50%) Easily manipulate hue

The modern format: oklch

oklch (Oklab Lightness Chroma Hue) is the recommended format for modern design:

color: oklch(70% 0.2 30);
/*          L    C   H
    L = Lightness (0% black, 100% white)
    C = Chroma (0 gray, 0.4 saturated)
    H = Hue (angle on the color wheel)
*/

Advantages over HSL:

  • More natural gradients: no gray banding that appears with HSL
  • Perceptually uniform: L=50% actually looks like 50% brightness
  • Easy palette creation: change only L to get lighter and darker variants of the same hue

Transparency in any format

Add a fourth value with / for opacity:

color: rgb(255 0 0 / 50%);
color: oklch(65% 0.2 25 / 80%);
color: #ff000080; /* 80 hex = 50% */

Gradients

Linear gradient

.background {
  background: linear-gradient(135deg, #ff530f, #e6286a);
}

The first argument is the direction: an angle (135deg) or keywords (to right, to bottom left).

Radial gradient

.background {
  background: radial-gradient(circle at top right, #ffc400, transparent 70%);
}

Conic gradient

.clock {
  background: conic-gradient(from 0deg, #ff530f, #e6286a, #b056ff, #ff530f);
  border-radius: 50%;
}

Text gradient

A popular visual effect is applying a gradient to text:

.gradient-text {
  background: linear-gradient(90deg, #ff530f, #e6286a);
  background-clip: text;
  color: transparent;
}

Backgrounds

background-image with overlay

To ensure text readability over images, use a gradient as an overlay:

.hero {
  background:
    linear-gradient(to bottom, rgb(0 0 0 / 60%), rgb(0 0 0 / 30%)),
    url("/img/hero.jpg") center / cover no-repeat;
}

Multiple backgrounds

CSS allows stacking multiple background images. They render in order: the first on top, the last at the bottom.

.decorative {
  background:
    url("pattern.svg") repeat,
    linear-gradient(135deg, #f0f0f0, #e0e0e0);
}

background-clip and background-origin

background-clip controls how far the background extends:

  • border-box (default): to the border
  • padding-box: to the padding
  • content-box: only the content area
  • text: only the text (for text gradients)

Dark theme with prefers-color-scheme

Use custom properties to create an automatic dark theme:

:root {
  --bg: oklch(98% 0 0);
  --text: oklch(15% 0 0);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: oklch(12% 0 0);
    --text: oklch(92% 0 0);
  }
}

color-mix(): mixing colors in CSS

The color-mix() function allows you to mix two colors without preprocessors:

.hover {
  /* 80% of the primary color + 20% black */
  background: color-mix(in oklch, var(--brand-primary) 80%, black);
}

With colors and backgrounds mastered, in the next lesson we will add motion to your interfaces with CSS transitions and animations.

Practice

  1. Create a palette with oklch: Define a 5-color palette in :root using oklch(). Generate lighter and darker variants of the same hue by changing only the lightness value.
  2. Implement an automatic dark theme: Use @media (prefers-color-scheme: dark) along with custom properties to redefine background and text colors automatically.
  3. Apply a text gradient: Create a heading with a gradient using background: linear-gradient(...), background-clip: text, and color: transparent.
Why oklch?
oklch produces perceptually uniform gradients (without the gray band that appears with rgb/hsl). Additionally, by changing only the first value (lightness), you can generate an entire coherent palette from a single hue.
Contrast and accessibility
Text must have a minimum contrast ratio of 4.5:1 against its background (WCAG AA). Use tools like the DevTools inspector or webaim.org/resources/contrastchecker to verify.
:root {
  /* Palette with oklch (best for design) */
  --brand-primary: oklch(75% 0.18 85);
  --brand-accent: oklch(65% 0.22 25);
  --text-primary: oklch(15% 0.01 260);
  --text-secondary: oklch(45% 0.01 260);
  --surface: oklch(98% 0.005 260);
  --surface-hover: oklch(95% 0.005 260);
}

/* Automatic dark theme */
@media (prefers-color-scheme: dark) {
  :root {
    --text-primary: oklch(95% 0.01 260);
    --text-secondary: oklch(70% 0.01 260);
    --surface: oklch(15% 0.01 260);
    --surface-hover: oklch(20% 0.01 260);
  }
}

/* Modern gradient for CTA */
.cta-button {
  background: linear-gradient(135deg,
    oklch(65% 0.25 25),
    oklch(55% 0.22 340)
  );
  color: white;
  border: none;
  padding: 0.75rem 2rem;
  border-radius: 8px;
}

/* Transparency with oklch */
.glass {
  background: oklch(98% 0.005 260 / 80%);
  backdrop-filter: blur(12px);
  border: 1px solid oklch(0% 0 0 / 8%);
}
/* Background image with overlay */
.hero {
  background:
    linear-gradient(to bottom,
      rgb(0 0 0 / 60%),
      rgb(0 0 0 / 30%)
    ),
    url("/img/hero.jpg") center / cover no-repeat;
  color: white;
  min-height: 60vh;
  display: grid;
  place-items: center;
}

/* Pure CSS geometric pattern */
.pattern {
  background-color: #f0f0f0;
  background-image:
    linear-gradient(45deg, #ddd 25%, transparent 25%),
    linear-gradient(-45deg, #ddd 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, #ddd 75%),
    linear-gradient(-45deg, transparent 75%, #ddd 75%);
  background-size: 20px 20px;
  background-position: 0 0, 0 10px, 10px -10px, -10px 0;
}

/* Text gradient */
.gradient-text {
  background: linear-gradient(90deg, #ff530f, #e6286a);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}