🎨 Colors, Backgrounds, and Gradients
Colour is one of the most powerful tools in visual communication — it sets mood, builds hierarchy, and carries brand. This lesson covers every way CSS lets you name a colour, how to paint element backgrounds, and how to generate rich gradients with zero image files.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Specify colour in hex, RGB(A), HSL(A), and modern formats, and pick the right one for the job
- Explain the HSL model and use it to build harmonious colour schemes
- Control backgrounds with
background-image,-repeat,-position,-size, and the shorthand - Create linear, radial, and conic gradients, including repeating variants
- Meet WCAG contrast requirements and keep backgrounds performant
Estimated Time: 40–50 minutes • Difficulty: Beginner
Hands-on: Build a pure-CSS hero section with a layered gradient background and readable text.
In This Lesson
Why Colour Matters
Colour establishes brand identity, creates visual hierarchy, improves usability, and sets emotional tone. A film director uses colour grading to steer your mood and attention; a web designer uses CSS colour the same way — to make an interface feel cohesive, intuitive, and alive.
💬 "Color does not add a pleasant quality to design — it reinforces it." — Pierre Bonnard. Colour works best in service of structure, not as decoration bolted on afterward.
CSS applies colour in many places: text (color), fills (background-color), edges (border-color), and effects (box-shadow, text-shadow). First, though, we need to agree on how to write a colour.
CSS Colour Formats
CSS accepts several notations for the same colour. They differ in readability, whether they support transparency, and how easy they make it to adjust a colour.
Named colours
color: black;
color: rebeccapurple; /* added in memory of Rebecca Meyer */
color: goldenrod;
There are 148 keywords — handy for quick prototyping, but too coarse for real design work.
Hexadecimal
color: #ff5733; /* RR GG BB in base-16, each 00–FF */
color: #f00; /* shorthand: expands to #ff0000 */
color: #ff573380; /* 8-digit hex adds an alpha byte (80 = 50%) */
Hex is the industry default and what most design tools export. Each pair is one channel (red, green, blue) from 00 to ff.
RGB and RGBA
color: rgb(255, 87, 51); /* legacy comma syntax */
color: rgba(255, 87, 51, 0.5); /* fourth value is alpha, 0–1 */
/* Modern space-separated syntax — rgb() now handles alpha too */
color: rgb(255 87 51);
color: rgb(255 87 51 / 50%);
HSL and HSLA
color: hsl(9, 100%, 60%); /* hue, saturation, lightness */
color: hsl(9 100% 60% / 50%); /* modern syntax with alpha */
HSL is the most human format for designers, which is why it gets its own section below.
Modern formats
color: hwb(9 20% 10%); /* hue, whiteness, blackness */
color: lch(60% 80 40); /* perceptually uniform lightness/chroma/hue */
color: oklch(0.7 0.15 40); /* OKLCH — great for consistent palettes */
color: color(display-p3 1 0.34 0.2); /* wider-gamut colour on capable screens */
✅ Which format when?
- Hex: the default; matches design tools and is compact.
- rgb() / hsl() with
/ alpha: whenever you need transparency. - HSL / OKLCH: when generating or systematically adjusting a palette.
- Named: throwaway prototypes only.
Thinking in HSL
HSL describes a colour by three intuitive dials, which makes it easy to derive lighter, darker, or related colours by hand.
- Hue (0–360°): position on the colour wheel — 0° red, 120° green, 240° blue.
- Saturation (0–100%): intensity — 0% is grey, 100% is fully vivid.
- Lightness (0–100%): 0% black, 50% the "true" colour, 100% white.
hsl(210 100% 20%) … hsl(210 100% 80%).Harmonies from the wheel
| Scheme | Recipe (by hue) | Feel |
|---|---|---|
| Monochromatic | One hue, vary S & L | Calm, cohesive |
| Analogous | Hues within ~30° of each other | Harmonious, natural |
| Complementary | Two hues 180° apart | High contrast, energetic |
| Triadic | Three hues 120° apart | Vibrant, balanced |
📖 Colour psychology (use as a starting point, not a rule)
Blue reads as trust and calm (finance, tech); green as growth and health; red as energy and urgency; black/gold/purple as luxury. Context and culture matter, so always test with real users.
Background Properties
The simplest background is a flat fill. From there, a family of properties positions and sizes background images (which include gradients).
.panel {
background-color: hsl(0 0% 96%);
/* When both color and image are set, the image paints on top of the color */
background-image: linear-gradient(to bottom, #ffffff, #f0f0f0);
}
The individual properties
.hero {
background-image: url("/img/mountains.jpg");
background-repeat: no-repeat; /* repeat | repeat-x | repeat-y | no-repeat | space | round */
background-position: center; /* keywords, %, lengths, or edge offsets */
background-size: cover; /* cover | contain | <length> | % */
background-attachment: fixed; /* scroll | fixed | local — fixed gives a parallax feel */
}
Value of background-size | Behaviour | Good for |
|---|---|---|
cover | Scale to fill the box, cropping overflow | Hero images, card thumbnails |
contain | Scale so the whole image fits, may leave gaps | Logos, icons that must stay whole |
100px 60px | Explicit width & height | Decorative patterns, UI sprites |
The background shorthand
/* color image repeat attachment position / size */
.card {
background: #333 url("/img/noise.png") no-repeat center / cover;
}
/* Multiple backgrounds stack: the FIRST layer sits on top */
.overlayed {
background:
linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)) no-repeat center / cover,
url("/img/photo.jpg") no-repeat center / cover;
}
⚠️ The shorthand resets omitted values
Like the font shorthand, background snaps every property you leave out back to its initial value. If you only want to change one thing (say the position), set the specific longhand instead.
CSS Gradients
Gradients are smooth colour transitions generated by the browser — no image request, infinitely scalable, and editable in code. They are technically background images, so they live in background-image.
Linear gradients
/* Default flows top → bottom */
background-image: linear-gradient(#e66465, #9198e5);
/* Direction: keyword or angle */
background-image: linear-gradient(to right, #e66465, #9198e5);
background-image: linear-gradient(45deg, #e66465, #9198e5);
/* Colour stops with positions */
background-image: linear-gradient(to right, red 0%, gold 50%, green 100%);
/* Hard stops make crisp stripes (no blend between the two 50% points) */
background-image: linear-gradient(to right, #4f46e5 50%, #a855f7 50%);
Radial gradients
/* Radiates outward from a point; default shape is an ellipse */
background-image: radial-gradient(circle, #e66465, #9198e5);
/* Control shape, size, and origin */
background-image: radial-gradient(circle closest-side at 25% 75%, #fff, #9198e5);
Conic gradients
/* Sweeps colours AROUND a centre — perfect for pie charts and colour wheels */
background-image: conic-gradient(#4f46e5, #a855f7, #ec4899, #4f46e5);
/* A two-slice pie using hard stops */
background-image: conic-gradient(#22c55e 0deg 90deg, #e5e7eb 90deg 360deg);
Repeating gradients & patterns
/* Diagonal stripes with no image file */
.stripes {
background-image: repeating-linear-gradient(
45deg, #606dbc, #606dbc 10px, #465298 10px, #465298 20px
);
}
/* A dot grid using a tiny radial gradient tile */
.dots {
background-image: radial-gradient(circle at 1px 1px, #333 1px, transparent 0);
background-size: 12px 12px;
}
Layering & Effects
Multiple backgrounds
Comma-separate values to stack layers. A classic pattern is a dark gradient over a photo so overlaid text stays readable:
.hero {
background:
linear-gradient(rgba(0,0,0,0.55), rgba(0,0,0,0.55)) no-repeat center / cover,
url("/img/city.jpg") no-repeat center / cover;
color: #fff;
}
background-clip: text
Clip a gradient to the shape of the text for an eye-catching heading:
.gradient-text {
background-image: linear-gradient(to right, #f06, #48f);
background-clip: text;
-webkit-background-clip: text; /* Safari still needs the prefix */
color: transparent;
font-weight: 800;
}
background-blend-mode
/* Blend a tint into a photo without editing the image */
.duotone {
background: url("/img/portrait.jpg") center / cover, #2563eb;
background-blend-mode: luminosity;
}
💡 Reach for CSS before images
Gradients, patterns, and blend modes render on the GPU, add zero network requests, and scale perfectly on any screen density. Prefer them over shipping a background JPEG whenever the effect can be expressed in CSS.
Contrast & Performance
Colour contrast (WCAG)
Readable colour is not optional. The Web Content Accessibility Guidelines set minimum contrast ratios between text and its background:
| Text | Minimum (AA) | Enhanced (AAA) |
|---|---|---|
| Normal body text | 4.5 : 1 | 7 : 1 |
| Large text (≥ 24px, or 18.66px bold) | 3 : 1 | 4.5 : 1 |
⚠️ Don't rely on colour alone
- Pair colour with text, icons, or patterns so colour-blind users get the message too (e.g. a red error also carries an ✕ icon).
- Check pairs with the WebAIM Contrast Checker.
- Over a gradient or photo background, test contrast against the lightest and darkest spots the text sits on.
Performance
- Prefer CSS gradients to image gradients — no HTTP request, smaller payload.
- Be cautious with large
background-attachment: fixedimages; they can cause scroll jank on mobile. - Use SVG data-URIs for crisp, tiny patterns.
- Alpha transparency is more expensive to composite than opaque fills — use it deliberately.
Hands-on Exercise
🏋️ Build a Pure-CSS Hero
Objective: Create a hero banner whose background is entirely CSS-generated and whose heading passes contrast.
Instructions:
- Make a full-width
<section class="hero">at least60vhtall containing an<h1>and a short tagline. - Give it a layered background: a translucent dark
linear-gradientover a colourfulradial-gradient— no image files. - Define your palette with HSL custom properties so you can retheme by editing one hue.
- Verify the heading colour hits at least 4.5 : 1 against the darkest part of the background.
- Bonus: add a subtle repeating-gradient dot pattern as a third layer.
💡 Hint
Layers are comma-separated in background, and the first one is on top. Put your dark overlay gradient first so it sits above the colourful base and tames it for text.
✅ Sample solution
.hero {
--brand-h: 220;
min-height: 60vh;
display: grid;
place-items: center;
text-align: center;
color: #fff;
padding: 2rem;
background:
radial-gradient(circle at 1px 1px, rgba(255,255,255,0.15) 1px, transparent 0) 0 0 / 16px 16px,
linear-gradient(rgba(0,0,0,0.45), rgba(0,0,0,0.55)),
radial-gradient(circle at 30% 30%,
hsl(var(--brand-h) 90% 55%),
hsl(calc(var(--brand-h) + 60) 80% 40%));
}
.hero h1 { font-size: clamp(2rem, 1rem + 5vw, 4rem); }
White text over the dark overlay comfortably exceeds 4.5 : 1. Change --brand-h to reskin the whole banner.
🎯 Quick Quiz
Question 1: In rgb(255 87 51 / 50%), what does the 50% control?
Question 2: Which gradient type sweeps colours around a centre point, making it ideal for pie charts?
Question 3: What is the minimum WCAG AA contrast ratio for normal-size body text?
Summary & Quiz
🎉 Key Takeaways
- CSS offers many colour formats: hex for defaults, rgb()/hsl() with alpha for transparency, HSL/OKLCH for building palettes.
- HSL separates hue, saturation, and lightness so you can derive scales and harmonies by hand.
- Backgrounds layer; the first comma-separated layer sits on top, and the shorthand resets what you omit.
- Linear, radial, and conic gradients (plus repeating variants) replace many image files entirely.
- Always meet WCAG contrast and never rely on colour alone to convey meaning.
📚 Further Reading
- MDN — Backgrounds and Borders
- web.dev — Learn CSS: Gradients
- Coolors palette generator • WebAIM Contrast Checker
🚀 What's Next?
With colour handled, we tackle how elements are sized and spaced: The Box Model in Depth — content, padding, border, margin, and the all-important box-sizing.
🎉 Great work!
You can now paint any element and build gradients from scratch. Next: how big that element actually is.