π¨ 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.
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; }
- Quick prototyping β readable and fast to type
- Learning and demos β no need to remember hex codes
- Special keywords β
transparent,currentColor,inherit
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 */
}
The alpha channel in 8-digit hex goes from 00 (transparent) to FF (opaque). Some common values:
| Hex alpha | Opacity |
|---|---|
FF | 100% (fully opaque) |
CC | 80% |
99 | 60% |
66 | 40% |
33 | 20% |
00 | 0% (fully transparent) |
- 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
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 */
}
- 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(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.
| Channel | Range | Meaning |
|---|---|---|
| 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 |
/* 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.
/* 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); }
: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);
}
- 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.
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.
| Channel | Range | Meaning |
|---|---|---|
| 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
/* 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 */
}
- 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
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 */
}
- SVG icons β set
fill: currentColorso 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 */
}
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.
: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:
| Level | Normal text | Large text (18px+ bold) |
|---|---|---|
| AA (minimum) | 4.5:1 | 3:1 |
| AAA (enhanced) | 7:1 | 4.5:1 |
Ratio: 4.48:1
Ratio: 7.0:1
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 */
}
- 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?
| Situation | Best format | Why |
|---|---|---|
| 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
- 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
colorvalue; essential for SVG icons and DRY styling - opacity vs alpha β
opacityaffects 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