CSS Colors Guide for Developers — Every Way to Write Color, Plus Dark Mode
Color in CSS looks simple until you inherit a stylesheet with keywords in one file, hex in another, rgba sprinkled through components, and no system tying them together. This guide covers every syntax you will meet, then shows the pattern that tames them all — custom properties as a palette — and finishes with a worked dark-mode setup you can paste into a project.
Every Way to Write a Color
color: rebeccapurple; /* 1. named keyword (148 exist) */ color: #663399; /* 2. hex */ color: #639; /* 3. hex shorthand */ color: #66339980; /* 4. hex with alpha */ color: rgb(102, 51, 153); /* 5. rgb */ color: rgb(102 51 153 / 0.5); /* 6. modern rgb + alpha */ color: hsl(270, 50%, 40%); /* 7. hsl */ color: hsl(270 50% 40% / 0.5); /* 8. modern hsl + alpha */ color: transparent; /* 9. fully transparent black */ color: currentColor; /* 10. the element's text color */
Notes on the less obvious ones:
- Keywords — fine for prototypes and tests ("red", "tomato"), rarely for production palettes since you cannot tune them. All 148 map to fixed hex values.
- transparent — shorthand for rgb(0 0 0 / 0). Handy for gradient endpoints and toggling borders without layout shift (border-color: transparent keeps the border's width).
- currentColor — resolves to the computed text color. Give an SVG icon fill="currentColor" and it follows the button's text automatically, in every state and theme. The single most underused keyword in CSS.
If the hex/rgb/hsl trio is still fuzzy, start with HEX vs RGB vs HSL explained — the rest of this guide assumes the basics.
Custom Properties: Your Palette as Code
Hard-coding #1e90ff in forty places means forty edits when the brand color changes. Define the palette once as custom properties on :root, and give the variables semantic names (what the color is for) rather than descriptive ones (what it looks like):
:root {
/* raw scale (optional layer) */
--blue-500: #1e90ff;
--blue-700: #1565c0;
--gray-100: #f1f5f9;
--gray-900: #0f172a;
/* semantic tokens — components use ONLY these */
--color-bg: #ffffff;
--color-surface: var(--gray-100);
--color-text: var(--gray-900);
--color-accent: var(--blue-500);
--color-accent-hover: var(--blue-700);
}
.button {
background: var(--color-accent);
color: var(--color-bg);
}
.button:hover { background: var(--color-accent-hover); }The two-layer trick (raw scale → semantic tokens) is what makes theming trivial later: dark mode only redefines the semantic layer.
Opacity vs Alpha — Not the Same Thing
A classic bug: "I made the card background translucent and now the text is faded too." That is the opacity property doing exactly its job — fading the entire element, children included. An alpha channel fades only the one color it belongs to:
/* WRONG for this goal: text fades with the box */
.card { background: #0f172a; opacity: 0.6; }
/* RIGHT: only the background is translucent */
.card { background: rgb(15 23 42 / 0.6); color: white; }Use opacity for fading whole elements in and out (transitions, disabled states); use alpha colors for translucent surfaces, overlays, and tints. Remember that translucent text over varying backgrounds also makes contrast unpredictable — see the WCAG contrast guide.
Gradient Basics
Gradients are images generated from colors, used anywhere an image works (usually background):
/* linear: direction, then color stops */ background: linear-gradient(135deg, #1e90ff, #9333ea); /* control stop positions */ background: linear-gradient(to right, #1e90ff 0%, #9333ea 80%); /* radial: from center outward */ background: radial-gradient(circle at top left, #1e90ff33, transparent); /* hard stop = stripes, no blend */ background: linear-gradient(90deg, #1e90ff 50%, #9333ea 50%);
Two practical tips: fading to transparent can show gray fringing in some browsers — fade to the same color with alpha 0 instead (e.g. #1e90ff00); and gradients between highly saturated complementary hues pass through muddy gray in sRGB, so add an intermediate stop if the middle looks dirty.
Dark Mode with prefers-color-scheme — Worked Tokens
Because components only reference semantic tokens, dark mode is a redefinition, not a rewrite:
:root {
--color-bg: #ffffff;
--color-surface: #f1f5f9;
--color-text: #0f172a;
--color-text-muted: #475569;
--color-accent: #1565c0; /* darker blue for light bg */
--color-border: #e2e8f0;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0b1120; /* near-black, blue-biased */
--color-surface: #1e293b; /* raised surfaces are LIGHTER */
--color-text: #e2e8f0; /* off-white, not pure #fff */
--color-text-muted: #94a3b8;
--color-accent: #60a5fa; /* lighter blue for dark bg */
--color-border: #334155;
}
}
body { background: var(--color-bg); color: var(--color-text); }The choices in that dark block are deliberate, not inverted:
- Backgrounds are dark gray-blue, not #000 — pure black creates harsh, smearing contrast on OLED screens
- Text is off-white — pure white on dark backgrounds produces glare/halation
- The accent gets lighter in dark mode — the same #1565c0 that passed contrast on white fails on #0b1120
- Elevation flips from shadows to lighter surfaces — surfaces get lighter, not darker, as they rise
Also set color-scheme: light dark; on :root so form controls and scrollbars follow the theme natively. For a user-facing toggle, add a class or data attribute that overrides the same tokens.
Frequently Asked Questions
The opacity property fades an entire element including its text and children, while an alpha channel (rgba, hsla, 8-digit hex) makes only that specific color translucent. For a see-through background with fully readable text, use a color with alpha, not opacity.
currentColor is a keyword that resolves to the element's computed text color. Borders, SVG icons, and box shadows using currentColor automatically follow the text color, so a single color change updates the whole component consistently.
Define semantic custom properties (--bg, --text, --accent) on :root, then redefine them inside a prefers-color-scheme: dark media query. Components reference only the variables, so the entire theme switches without touching component CSS.