🎨 CSS Colors

Color is one of the most powerful tools in design. CSS gives you several ways to express color β€” each with different strengths. Choosing the right format affects readability, maintainability, theming, and even accessibility.

πŸ“ Where colors are used

Color values appear in many CSS properties:

.element {
  color: red;               /* text color */
  background-color: blue;   /* background */
  border-color: green;      /* border */
  outline-color: orange;    /* outline */
  box-shadow: 0 2px 8px purple; /* shadow */
  text-decoration-color: pink; /* underline */
  caret-color: teal;         /* text cursor in inputs */
}

Named Colors (Keywords)

CSS defines 148 named color keywords β€” human-readable names that map to specific color values.

h1 { color: tomato; }
p  { color: steelblue; }
nav { background-color: midnightblue; }
button { background-color: coral; }
tomato
steelblue
midnightblue
coral
lightseagreen
mediumpurple
mediumseagreen
hotpink
βœ… When to use named colors
  • Quick prototyping β€” readable and fast to type
  • Learning and demos β€” no need to remember hex codes
  • Special keywords β€” transparent, currentColor, inherit
⚠️ Avoid in production design

Named colors rarely match a real brand palette. They're hard to adjust (you can't slightly darken tomato) and are inconsistent across designers. Use hex, HSL, or oklch for real projects.

Hexadecimal β€” #rrggbb

Hex is the most common format on the web. It encodes red, green, and blue channels as pairs of hexadecimal digits (00–FF), where 00 = 0 and FF = 255.

/* Full 6-digit hex */
.element {
  color: #ff6347;  /* red=255, green=99, blue=71 β†’ tomato */
  color: #4a90e2;  /* a calm blue */
  color: #333333;  /* dark gray */
  color: #ffffff;  /* white */
  color: #000000;  /* black */
}

/* 3-digit shorthand β€” when each pair is the same digit */
.element {
  color: #333;   /* same as #333333 */
  color: #fff;   /* same as #ffffff */
  color: #f00;   /* same as #ff0000 β€” pure red */
}

/* 8-digit hex with alpha transparency */
.overlay {
  background-color: #00000080; /* black at 50% opacity */
  background-color: #4a90e2cc; /* blue at ~80% opacity */
}
πŸ“ How hex digits map to opacity

The alpha channel in 8-digit hex goes from 00 (transparent) to FF (opaque). Some common values:

Hex alphaOpacity
FF100% (fully opaque)
CC80%
9960%
6640%
3320%
000% (fully transparent)
βœ… When to use hex
  • Copying colors from Figma, design tools, or brand guidelines β€” they output hex
  • Static palettes that don't need programmatic adjustment
  • When working with a designer who provides hex codes
⚠️ Hex is hard to reason about

Looking at #4a90e2 tells you nothing about the color intuitively. You can't tell if it's light or dark, saturated or muted, or how to make it slightly lighter. For that, HSL or oklch are far better.

RGB and RGBA

RGB expresses the same red–green–blue model as hex, but with decimal numbers (0–255) that are easier to read and modify in code.

/* rgb(red, green, blue) β€” values 0–255 */
.element {
  color: rgb(255, 99, 71);    /* tomato */
  color: rgb(74, 144, 226);   /* calm blue */
  color: rgb(0, 0, 0);        /* black */
  color: rgb(255, 255, 255);  /* white */
}

/* rgba adds alpha (opacity) as a 4th value: 0.0–1.0 */
.overlay {
  background-color: rgba(0, 0, 0, 0.5);      /* 50% black */
  background-color: rgba(74, 144, 226, 0.2); /* light blue tint */
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

/* Modern syntax: rgb() handles alpha too (no need for rgba) */
.element {
  color: rgb(74 144 226 / 80%);  /* space-separated, % alpha */
}
βœ… When to use rgb / rgba
  • JavaScript color manipulation β€” easy to read/write individual channels
  • Transparent overlays and shadows β€” rgba(0,0,0,0.5) is the standard pattern
  • When you need to interpolate channels with JS (e.g., animated color transitions)
⚠️ RGB still isn't intuitive for design

rgb(74, 144, 226) doesn't tell you "this is a medium-light blue". Adjusting lightness means changing all three channels simultaneously. For design decisions, HSL or oklch are much more natural.

Practical RGBA patterns

/* Modal backdrop */
.modal-backdrop {
  background-color: rgba(0, 0, 0, 0.6);
}

/* Subtle card shadow */
.card {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12),
              0 8px 24px rgba(0, 0, 0, 0.08);
}

/* Highlight on hover without changing the color */
.nav-item:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

HSL and HSLA

HSL stands for Hue, Saturation, Lightness β€” a model designed to match how humans think about color, making it far more intuitive for design work.

ChannelRangeMeaning
Hue 0–360Β° The color on the wheel: 0=red, 120=green, 240=blue
Saturation 0%–100% 0% = gray, 100% = fully vibrant
Lightness 0%–100% 0% = black, 50% = normal, 100% = white
0Β°
30Β°
60Β°
90Β°
120Β°
150Β°
180Β°
210Β°
240Β°
270Β°
300Β°
330Β°
/* hsl(hue, saturation%, lightness%) */
.element {
  color: hsl(9, 100%, 64%);    /* tomato red */
  color: hsl(214, 70%, 59%);  /* calm blue */
  color: hsl(120, 60%, 40%);  /* forest green */
}

/* hsla adds alpha transparency */
.element {
  background: hsla(214, 70%, 59%, 0.2); /* light blue tint */
}

/* Modern syntax (no need for hsla) */
.element {
  background: hsl(214 70% 59% / 20%);
}

The real power of HSL: easy adjustments

Keep the hue fixed and vary saturation/lightness to build an entire color palette from a single color β€” something impossible to do intuitively with hex or RGB.

Building a full button palette from one hue
/* All the same hue (214Β° = blue), only lightness changes */
:root {
  --blue-900: hsl(214, 70%, 15%); /* very dark */
  --blue-700: hsl(214, 70%, 35%);
  --blue-500: hsl(214, 70%, 55%); /* base */
  --blue-300: hsl(214, 70%, 75%);
  --blue-100: hsl(214, 70%, 92%); /* very light tint */
}

.btn        { background: var(--blue-500); }
.btn:hover  { background: var(--blue-700); } /* darker on hover */
.btn:active { background: var(--blue-900); } /* darkest on press */
.btn-ghost  { background: var(--blue-100); color: var(--blue-700); }
Semantic UI colors from one hue per meaning
:root {
  /* Same structure, different hues */
  --success:     hsl(142, 60%, 45%); /* green */
  --success-bg:  hsl(142, 60%, 94%);
  --warning:     hsl(38,  95%, 50%); /* amber */
  --warning-bg:  hsl(38,  95%, 94%);
  --danger:      hsl(4,   86%, 52%); /* red */
  --danger-bg:   hsl(4,   86%, 95%);
  --info:        hsl(214, 70%, 55%); /* blue */
  --info-bg:     hsl(214, 70%, 95%);
}

.alert-success {
  color: var(--success);
  background: var(--success-bg);
  border-left: 4px solid var(--success);
}
βœ… When to use HSL
  • Building color palettes β€” spin the hue, vary the lightness
  • Semantic UI colors β€” success, warning, danger, info
  • Dark mode β€” flip lightness values, keep hues the same
  • Hover and active states β€” just change the lightness
  • When you want to reason about color without a color picker

oklch β€” The Modern Standard

oklch is the newest CSS color format and is increasingly considered the best choice for design systems. It stands for OK Lightness Chroma Hue β€” a perceptually uniform color space.

πŸ“ What "perceptually uniform" means

In HSL, two colors with the same lightness value can look very different in brightness to the human eye β€” a yellow at hsl(60, 100%, 50%) appears much brighter than a blue at hsl(240, 100%, 50%), even though both have 50% lightness.

oklch fixes this: colors at the same L value genuinely look equally bright to humans. This makes it reliable for accessibility and automatic palette generation.

ChannelRangeMeaning
L (Lightness) 0–1 (or 0%–100%) Perceptually uniform brightness: 0=black, 1=white
C (Chroma) 0–0.4+ Color intensity/vividness: 0=gray, higher=more vivid
H (Hue) 0–360Β° Same color wheel as HSL
/* oklch(lightness chroma hue) */
.element {
  color: oklch(0.63 0.26 29);    /* vivid red-orange */
  color: oklch(0.55 0.22 264);   /* medium blue */
  color: oklch(0.72 0.17 142);   /* fresh green */
  color: oklch(0.2 0 0);         /* near-black (no chroma = gray) */
}

/* With alpha */
.overlay {
  background: oklch(0 0 0 / 50%);
}

oklch palette generation

Perceptually consistent shade scale
/* Fix hue and chroma, only vary lightness.
   Every step looks equally different to the eye. */
:root {
  --brand-900: oklch(0.25 0.20 264); /* darkest */
  --brand-700: oklch(0.40 0.20 264);
  --brand-500: oklch(0.55 0.20 264); /* base */
  --brand-300: oklch(0.70 0.20 264);
  --brand-100: oklch(0.92 0.05 264); /* near-white tint */
}
βœ… When to use oklch
  • Design systems and component libraries β€” predictable, perceptually even palettes
  • Accessible color generation β€” lightness value reliably predicts contrast
  • Vibrant colors β€” oklch can express colors outside the sRGB gamut on wide-gamut displays (P3)
  • When you want to programmatically generate palettes in CSS or code
πŸ“ Browser support

oklch is supported in all modern browsers (Chrome, Firefox, Safari, Edge). For older browser support, provide a hex fallback:

.button {
  background: #4a90e2;                  /* fallback */
  background: oklch(0.55 0.22 264);    /* modern */
}

currentColor β€” The Inherited Color Keyword

currentColor is a special keyword that equals the element's current color property. It lets other properties follow the text color automatically.

/* Without currentColor β€” you'd repeat the color */
.icon {
  color: #4a90e2;
  border: 2px solid #4a90e2;  /* duplicated! */
  box-shadow: 0 0 8px #4a90e2; /* duplicated! */
}

/* With currentColor β€” stays in sync automatically */
.icon {
  color: #4a90e2;
  border: 2px solid currentColor;
  box-shadow: 0 0 8px currentColor;
}

/* Great for SVG icons that should match text color */
svg {
  fill: currentColor;
}

/* Changing the parent color updates everything */
.btn:hover .icon {
  color: white; /* border and fill also become white automatically */
}
βœ… When to use currentColor
  • SVG icons β€” set fill: currentColor so icons adapt to any text color
  • Borders that match text β€” underlines, outlines, decorative lines
  • Themed components β€” one color change propagates to all related properties

Transparency: opacity vs alpha

There are two ways to make something transparent in CSS β€” and they behave differently.

/* opacity: affects the ENTIRE element including children */
.modal {
  opacity: 0.5; /* text inside also becomes 50% transparent */
}

/* Alpha channel: affects only the COLOR, children unaffected */
.overlay {
  background-color: rgba(0, 0, 0, 0.5); /* background semi-transparent */
  color: white; /* text stays fully opaque */
}
⚠️ opacity is inherited by children

If you set opacity: 0.5 on a card, all its child elements β€” text, images, buttons β€” also become 50% transparent. Use alpha on the background color instead when you only want the background to be see-through.

CSS Custom Properties for Color Systems

Custom properties (CSS variables) are the most powerful way to manage colors in a real project. They let you define your palette once and reference it everywhere β€” making theming and dark mode trivial.

Complete color system with light + dark mode
:root {
  /* Brand palette */
  --color-brand-500: hsl(214, 70%, 55%);
  --color-brand-600: hsl(214, 70%, 45%);
  --color-brand-100: hsl(214, 70%, 95%);

  /* Semantic tokens β€” these are what components use */
  --color-bg:       hsl(0, 0%, 100%);
  --color-surface:  hsl(220, 20%, 97%);
  --color-text:     hsl(220, 15%, 20%);
  --color-text-muted: hsl(220, 10%, 50%);
  --color-border:   hsl(220, 15%, 88%);
  --color-primary:  var(--color-brand-500);
  --color-primary-hover: var(--color-brand-600);
}

/* Dark mode β€” override only the semantic tokens */
@media (prefers-color-scheme: dark) {
  :root {
    --color-bg:       hsl(220, 15%, 10%);
    --color-surface:  hsl(220, 15%, 14%);
    --color-text:     hsl(220, 20%, 90%);
    --color-text-muted: hsl(220, 10%, 60%);
    --color-border:   hsl(220, 15%, 25%);
    /* --color-primary stays the same */
  }
}

/* Components use tokens, never raw colors */
body {
  background-color: var(--color-bg);
  color: var(--color-text);
}

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
}

.btn-primary {
  background: var(--color-primary);
}
.btn-primary:hover {
  background: var(--color-primary-hover);
}

Color and Accessibility

Around 8% of men and 0.5% of women have some form of color vision deficiency. Never rely on color alone to convey information β€” and always ensure sufficient contrast.

Contrast ratio

WCAG (Web Content Accessibility Guidelines) defines minimum contrast ratios between text and its background:

LevelNormal textLarge text (18px+ bold)
AA (minimum)4.5:13:1
AAA (enhanced)7:14.5:1
❌ Fail β€” #767676 on white
Ratio: 4.48:1
βœ… AA Pass β€” #595959 on white
Ratio: 7.0:1
βœ… Pass β€” white on blue
Ratio: 3.5:1 (large text)
/* ❌ Low contrast β€” hard to read for many users */
p {
  color: hsl(0, 0%, 55%);       /* light gray on white */
  background: hsl(0, 0%, 100%);  /* contrast: ~3.5:1 β€” fails AA */
}

/* βœ… Sufficient contrast */
p {
  color: hsl(0, 0%, 25%);       /* dark gray β€” contrast: ~10:1 */
  background: hsl(0, 0%, 100%);
}

/* βœ… Don't rely on color alone β€” add an icon or label too */
.error-message {
  color: hsl(4, 86%, 40%);
  /* Also show an icon: ❌ Error: field is required */
}
πŸ”§ Tools to check contrast
  • Browser DevTools β€” hover over a color in the inspector to see contrast ratio
  • oklch lightness as a guide β€” L above 0.6 on white starts failing AA for body text

Gradients

Gradients are generated images that transition between colors. They use the same color values as everything else.

/* Linear gradient β€” left to right */
.hero {
  background: linear-gradient(to right, hsl(214, 70%, 55%), hsl(270, 70%, 55%));
}

/* Linear gradient β€” diagonal */
.card {
  background: linear-gradient(135deg, #667eea, #764ba2);
}

/* Radial gradient */
.spotlight {
  background: radial-gradient(circle, hsl(55, 100%, 70%), hsl(40, 100%, 50%));
}

/* Transparent fade β€” text readability over images */
.image-overlay {
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.8) 0%,
    rgba(0, 0, 0, 0) 60%
  );
}

/* Subtle background texture with stops */
.section {
  background: linear-gradient(
    to bottom,
    hsl(220, 20%, 97%),
    hsl(220, 20%, 100%)
  );
}

Quick Reference: Which Format to Use?

SituationBest formatWhy
Copying from a design tool (Figma) #hex Design tools export hex β€” use it as-is
Building a color palette hsl() or oklch() Easy to adjust lightness and saturation
Semantic UI colors (success, danger…) hsl() One hue per meaning, vary lightness for states
Transparent overlays / shadows rgba() or hsl() / % Alpha channel is intuitive (0–1 or 0%–100%)
Design system / component library oklch() Perceptually uniform, wide-gamut capable
Dark mode theming CSS custom properties + hsl() Override variables in a media query, done
SVG icons that match text currentColor Inherits from color automatically
Quick prototype / learning Named colors Readable, no memorisation needed
JavaScript color manipulation rgb() Easy to destructure and recombine channels

Summary

πŸ“š What you learned
  • Named colors β€” 148 keywords; great for prototyping, not production
  • Hex β€” #rrggbb; most common, used in design tools; add 2 digits for alpha (#rrggbbaa)
  • RGB / RGBA β€” decimal channels; best for shadows, overlays, JS manipulation
  • HSL / HSLA β€” Hue / Saturation / Lightness; most intuitive for building palettes and states
  • oklch β€” perceptually uniform; the modern standard for design systems and accessible palettes
  • currentColor β€” inherits color value; essential for SVG icons and DRY styling
  • opacity vs alpha β€” opacity affects children too; alpha on a color property does not
  • CSS custom properties β€” define your palette once, theme everywhere including dark mode
  • Contrast ratio β€” 4.5:1 minimum for body text (WCAG AA); test with DevTools