Colors & typography.
The details that make pages feel polished.
Two sites can have identical layouts and feel completely different based on colors and typography. This is the layer most developers rush through and then wonder why their pages look "off." Learn the color formats, the type scale, the line-height that makes text readable, and the font choices that make a site feel professional — instead of like a template.
Colors come in five formats: hex (#3b82f6), rgb (rgb(59,130,246)), hsl (hsl(217,91%,60%)), named (blue), and modern oklch. Fonts: use system-ui for speed, or a custom font from Google Fonts. The three properties that matter most: font-size (size), line-height (1.5 for body), font-weight (400 default, 700 bold). Get these right and every page looks better.
Before this lesson: complete Lesson 2. You need to understand sizing before styling text inside the boxes.
🎨 Colors — five ways to write the same thing
Every format produces a color. Some are more useful for specific jobs.
/* Named color — 148 pre-defined names */
.named {
background: crimson;
color: white;
}
/* Hex — 6-digit (or 3-digit shortcut) */
.hex {
background: #3b82f6;
color: #ffffff;
}
/* RGB — red, green, blue values 0-255 */
.rgb {
background: rgb(59, 130, 246);
color: rgb(255, 255, 255);
}
/* RGBA — same as RGB with alpha (0-1) /
.rgba {
background: rgba(59, 130, 246, 0.5); / 50% transparent */
color: white;
}
/* HSL — hue (0-360), saturation %, lightness % */
.hsl {
background: hsl(217, 91%, 60%);
color: white;
}
/* HSL is easier to tweak — change lightness for hover /
.hsl-hover {
background: hsl(217, 91%, 60%); / base: 60% lightness /
}
.hsl-hover:hover {
background: hsl(217, 91%, 45%); / hover: 15% darker */
}
/* Modern: oklch — better color science, wider gamut */
.oklch {
background: oklch(0.65 0.18 250);
color: white;
}
All produce a similar blue — except the named one (crimson). The format is a choice; the color is what matters.
hex (#3b82f6)
The default. Shortest format. Two characters each for red, green, blue. #3b82f6 = red 59, green 130, blue 246. Use for solid colors — the most common format in real code.
hsl (hsl(217,91%,60%))
Hue, saturation, lightness. Easier to reason about: same hue + different lightness = hover state. Use for generating color shades and building design systems.
rgba (rgba(59,130,246,0.5))
RGB with alpha transparency. The 4th value (0–1) controls opacity. Use for overlays, backgrounds, and any color that should blend with what's behind it.
oklch (modern)
Perceptually uniform color space. Same lightness value feels equally bright across all hues — unlike HSL. Supported in all modern browsers. Use for design systems where color consistency matters.
The 60-30-10 rule. Most well-designed sites follow this ratio: 60% neutral (white/gray backgrounds), 30% secondary (text, borders), 10% accent (buttons, links, highlights). Beginners often invert it — 60% accent color everywhere — which looks loud and cheap. Use one accent color, sparingly. Everything else should be neutral grays. This single rule will improve your designs more than any other color advice.
Contrast is required, not optional. WCAG AA requires 4.5:1 contrast for body text, 3:1 for large text (18px+ bold or 24px+ regular). Light gray text on white — very common in beginner designs — fails this and is hard to read. Check any color pair at webaim.org/resources/contrastchecker;. Aim for #1e293b (dark slate) on #f8fafc (near-white) for body text — that's a 15:1 ratio, easy to read.
🅰️ Fonts — the fastest way to look professional
Font stacks, system fonts, and custom fonts. The tradeoffs.
/* System font stack — fast, native, zero load time */
.system {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
}
/* Serif — traditional, editorial, print-like */
.serif {
font-family: Georgia, 'Times New Roman', serif;
}
/* Monospace — code, terminal, technical feel */
.mono {
font-family: 'SF Mono', Consolas, Monaco, 'Courier New', monospace;
}
/* Custom font (via Google Fonts in the HTML head) */
.custom {
font-family: 'Inter', system-ui, sans-serif;
}
/* Font sizes — px is fine, rem is better for accessibility /
.small { font-size: 0.875rem; } / 14px /
.base { font-size: 1rem; } / 16px — the browser default /
.large { font-size: 1.25rem; } / 20px /
.h1 { font-size: 2rem; } / 32px /
.h0 { font-size: 3rem; } / 48px */
/* Font weights — 100 (thin) to 900 (black) /
.weight-300 { font-weight: 300; } / light /
.weight-400 { font-weight: 400; } / regular /
.weight-600 { font-weight: 600; } / semibold /
.weight-700 { font-weight: 700; } / bold /
.weight-900 { font-weight: 900; } / black */
system-ui: This is what your OS uses for its own UI. San Francisco on Mac, Segoe UI on Windows.
serif (Georgia): Traditional, editorial — like a book or newspaper.
monospace: const code = "fixed width";
font-weight 300 (light)
font-weight 400 (regular)
font-weight 600 (semibold)
font-weight 700 (bold)
font-weight 900 (black)
system-ui
The fastest, most native-looking option. Uses the OS's own UI font. Zero loading time. The best default for most sites in 2026 — every major site has moved to it.
font-family stack
List multiple fonts separated by commas. The browser uses the first that's available. Always end with a generic (sans-serif, serif, monospace).
rem vs px
rem is relative to the root font size (16px by default). 1.5rem = 24px. Use rem for sizes — it respects user accessibility settings. px doesn't scale when users zoom the page.
font-weight
100 (thin) to 900 (black). Not every font supports every weight — check the font. System fonts usually have 400 and 700 only. Custom fonts can offer more, but each weight is an extra file to download.
📝 Text — the properties that matter
Six properties cover 90% of text styling. Line-height is the most important.
.body-text {
/* Line-height — THE most important text property /
line-height: 1.5; / 1.5× the font size — good for body /
/ Headings typically use 1.2 (tighter) /
/ Long text (articles) sometimes uses 1.6-1.7 */
/* Text alignment /
text-align: left; / default — best for body text /
/ center — good for headings, bad for paragraphs /
/ right — good for numbers, RTL languages /
/ justify — formal but creates uneven spacing */
/* Letter spacing — adjust for large headlines only /
letter-spacing: normal; / default /
/ -0.02em on large headlines — looks more polished /
/ 0.05em on ALL-CAPS labels — improves readability */
/* Text transform /
text-transform: none;
/ uppercase, lowercase, capitalize */
/* Text decoration /
text-decoration: none; / remove underlines from links /
/ underline, line-through */
/* Text color /
color: #1e293b; / dark slate on light backgrounds */
}
.heading {
line-height: 1.2;
letter-spacing: -0.02em;
font-weight: 700;
}
.label {
font-size: 0.75rem;
letter-spacing: 0.05em;
text-transform: uppercase;
font-weight: 700;
color: #64748b;
}
line-height: 1.5 — This paragraph uses the recommended line-height for body text. Notice how each line has breathing room, making it comfortable to read multiple lines without the text feeling cramped or crowded. This is the single most impactful property for readability.
line-height: 1.1 — Same text, too tight. Lines feel cramped. Common mistake when beginners set line-height too small on headings that get used for paragraphs.
line-height: 2.0 — Too loose. Lines feel disconnected. Each line floats separately instead of forming a paragraph.
Section Label (uppercase, tracked)
Large heading with tight spacing
line-height
The single most important text property. 1.5 for body text — the sweet spot for readability. 1.2 for headings (they can be tighter). Unitless values scale with font size.
text-align
left for body. center for short headlines and hero sections. justify creates uneven gaps on web — rarely worth it. Never center long paragraphs.
letter-spacing
Negative for large headlines (−0.02em makes them look tighter and more polished). Positive for ALL-CAPS labels (+0.05em improves readability). Normal for everything else.
text-transform
Change case without changing HTML. Use uppercase for section labels, category tags, and small caps. Capitalize for titles. Use sparingly — all-caps is harder to read in long chunks.
Fix line-height once, at the top. Set body { line-height: 1.5; } — this makes every paragraph on the page readable with one rule. Then override just the headings: h1, h2, h3 { line-height: 1.2; }. That's the entire typography system for 90% of sites. No need to set line-height on every element — inheritance handles it.
📐 The type scale
A consistent set of sizes makes pages feel designed. Random sizes make them feel broken.
/* A type scale — pick one ratio and apply consistently /
/ Common ratios: 1.2 (subtle), 1.25 (major third), 1.333 (perfect fourth) */
:root {
--text-xs: 0.75rem; /* 12px /
--text-sm: 0.875rem; / 14px /
--text-base: 1rem; / 16px — body text /
--text-lg: 1.125rem; / 18px /
--text-xl: 1.25rem; / 20px /
--text-2xl: 1.5rem; / 24px /
--text-3xl: 1.875rem; / 30px /
--text-4xl: 2.25rem; / 36px /
--text-5xl: 3rem; / 48px /
--text-6xl: 3.75rem; / 60px */
}
body {
font-size: var(--text-base);
line-height: 1.5;
}
h1 {
font-size: var(--text-5xl);
line-height: 1.1;
letter-spacing: -0.02em;
font-weight: 800;
}
h2 {
font-size: var(--text-3xl);
line-height: 1.2;
letter-spacing: -0.01em;
font-weight: 700;
}
h3 {
font-size: var(--text-xl);
line-height: 1.3;
font-weight: 600;
}
p {
font-size: var(--text-base);
line-height: 1.6;
margin-bottom: 1rem;
}
small, .caption {
font-size: var(--text-sm);
color: #64748b;
}
code, .code {
font-size: 0.9em; /* slightly smaller than surrounding text */
font-family: 'SF Mono', Consolas, monospace;
background: rgba(0,0,0,0.1);
padding: 0.1em 0.3em;
border-radius: 0.25em;
}
Page heading (h1)
Section heading (h2)
Subsection heading (h3)
Body paragraph — this is the default text size users read. It's the baseline that all other sizes relate to. Everything above and below should feel like intentional variation from this base.
Small text — for captions, metadata, secondary info.
Extra small — for footnotes, legal text. Use sparingly.
Code inline: const x = 5; looks distinct from surrounding text.
Why a scale?
Random font sizes (17px, 22px, 25px, 29px) look messy. A scale (14, 16, 18, 20, 24, 30, 36) creates predictable rhythm. Every element relates to the others mathematically.
CSS custom properties
Define sizes as --text-base, --text-xl, etc. on :root. Then use var(--text-xl) everywhere. Change the scale in one place, all sizes update.
Heading weights
h1 = 800, h2 = 700, h3 = 600. Weight decreases as headings get smaller. This creates visual hierarchy — bigger headings are also bolder, so users scan them instantly.
Line-height per element
Big text needs tighter line-height (1.1–1.2), body text needs looser (1.5–1.6). The bigger the font, the tighter the line-height should be. This is counterintuitive but true.
Steal the Tailwind type scale. If you don't want to design your own scale, use Tailwind's — it's 0.75, 0.875, 1, 1.125, 1.25, 1.5, 1.875, 2.25, 3, 3.75rem. It's been tested on millions of sites and matches what users expect. You don't have to use Tailwind the framework — you can just copy the size values into your own CSS variables.
🖼️ Backgrounds — color, image, gradient
Three types. All set with background or background-* properties.
/* Solid color background */
.bg-color {
background: #1572B6;
color: white;
padding: 20px;
}
/* Gradient background */
.bg-gradient {
background: linear-gradient(135deg, #1572B6, #33A9DC);
color: white;
padding: 20px;
}
/* Gradient with transparent overlay on an image */
.bg-image-overlay {
background:
linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)),
url('https://picsum.photos/600/200');
background-size: cover;
background-position: center;
color: white;
padding: 40px 20px;
}
/* Radial gradient */
.bg-radial {
background: radial-gradient(circle at 30% 30%, #3b82f6, #1e293b);
color: white;
padding: 20px;
}
/* Repeating pattern */
.bg-pattern {
background:
repeating-linear-gradient(
45deg,
transparent,
transparent 10px,
rgba(59,130,246,0.1) 10px,
rgba(59,130,246,0.1) 20px
);
color: #e2e8f0;
padding: 20px;
}
background-color
Solid fill. background: #1572B6; is shorthand for background-color. The simplest — use it 95% of the time.
linear-gradient
Two or more colors in a direction. linear-gradient(135deg, red, blue) goes diagonally. Use for hero sections, buttons, and decorative elements.
background-size: cover
For image backgrounds — fills the element without distortion. Crops as needed. Always pair with background-position: center to anchor the important part.
Overlays
Dark image with white text is unreadable. Fix: layer a semi-transparent gradient on top. linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url(image.jpg). The gradient goes first (on top).
Text on background images must have contrast. If you put text on a photo, add a dark overlay so the text reads. The overlay should be dark enough that the text has at least 4.5:1 contrast against the darkest part of the image. Without the overlay, white text on a light image (or dark text on a dark image) is illegible. The overlay pattern above is the standard solution.
🏆 Challenge
Three tasks. Each exercises color and typography skills.
Task 1 — Build a color palette
Create a color palette for a fictional brand. Define it as CSS custom properties on :root:
1. --color-primary — the main brand color
2. --color-primary-hover — 15% darker, using HSL
3. --color-text — dark color for body text
4. --color-text-muted — lighter, for secondary text
5. --color-bg — page background
6. --color-border — subtle borders
Then use these variables in a small card. Change the primary color to a new value — everything should update. That's the power of a design system.
Task 2 — Type scale in action
Define a type scale as custom properties (like the example above). Then build a page with:
1. An h1 using the 3rem size, tight letter-spacing
2. Two h2 sections using the 1.875rem size
3. Body paragraphs using 1rem, line-height 1.5
4. A caption using 0.875rem, muted color
Verify: every size should relate mathematically to the base. Change the base from 1rem to 1.125rem — everything scales proportionally (if you use rem-based values).
Task 3 — Hero section with background
Build a hero section (full-width banner) with:
1. A gradient or image background
2. Dark overlay so text is readable
3. Large white heading (h1)
4. Subtitle paragraph (slightly smaller, slightly transparent)
5. A call-to-action button below
6. All text centered
Test the contrast — use DevTools' accessibility checker or webaim.org to verify the white text has 4.5:1 contrast against the darkest part of the background. Add more overlay opacity if it doesn't.
⚠️ Common mistakes
The color and typography bugs that catch every beginner.
Using 3+ font families on one page looks chaotic. Fix: 1-2 fonts max. One for headings, one for body. Or the same system font for both — that's what most modern sites do.
Setting line-height: 1.2 on paragraphs makes them unreadable. Fix: 1.5 for body, 1.2 for headings. It's the single most impactful typography fix.
Light gray on white fails WCAG. Fix: use #1e293b on #f8fafc (dark slate on near-white) — 15:1 contrast. Test every color pair at webaim.org.
Centered text has ragged left AND right edges — hard to read in paragraphs. Fix: text-align: left for body, center for short headlines only.
Some sizes in px, some in rem — inconsistent scaling when users zoom. Fix: use rem for font sizes, px for borders. One rule, applied consistently.
12px body text is unreadable, especially on mobile. Fix: 16px for body, 14px for small, 12px only for legal footnotes. Never smaller than 12px.
Default line-height on an h1 creates huge gaps between lines. Fix: h1, h2, h3 { line-height: 1.2; }. Big text needs tight line-height.
color: lightblue; works. color: navyblue; doesn't — it's navy or darkblue. Check the 148 named colors on MDN or use hex/hsl.
➡️ What's next
You can style individual elements. Next: arrange many of them.
❓ Frequently asked questions
What are the different ways to write colors in CSS?
CSS has five ways: named colors (red, blue, rebeccapurple), hex (#ff0000), rgb (rgb(255,0,0)), hsl (hsl(0,100%,50%)), and modern color functions like oklch. All produce the same visual result. Hex is shortest. HSL is easier to tweak. Use hex for solid colors, hsl for computed colors like hover states.
What is the best font stack for a website?
System font stacks are fastest: font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif. This uses the OS's default UI font — San Francisco on Mac, Segoe UI on Windows. Zero loading time. For custom fonts, use Google Fonts with font-display: swap.
What is line-height and why does it matter?
line-height is the vertical space each line of text occupies. It's the single most impactful typography property for readability. Sweet spot: 1.5 for body text, 1.2 for headings. Unitless values scale with font size — line-height: 1.5 means '1.5 times the font size'.
What is the difference between serif and sans-serif?
Serif fonts have small decorative lines at the ends of letters (Times New Roman, Georgia). Sans-serif fonts don't (Arial, Helvetica, system-ui). Both are readable; sans-serif is more common on screens because it renders cleaner at small sizes. Modern UI design overwhelmingly uses sans-serif for body text.
What is the difference between font-weight and font-size?
font-weight controls the boldness — 100 is thin, 400 is regular, 700 is bold, 900 is black. font-size controls the height of the text. They're independent: a bold 12px font looks different from a bold 24px font. Use font-weight to emphasize, font-size to establish hierarchy.
How do I use a custom Google Font?
Add a link to the font in your HTML head: link href='https://fonts.googleapis.com/css2?family=Inter&display=swap' rel='stylesheet', then use it in CSS: font-family: 'Inter', sans-serif. Add font-display: swap to show a fallback immediately while the custom font loads.
What is text-align and when should I use each value?
text-align controls horizontal alignment. left is default — easiest to read for body text. center looks formal or promotional — good for headlines, bad for paragraphs. right is rare (numeric columns, RTL languages). justify stretches to fill the width — looks formal in print but creates uneven spacing on web.
How do I set a background image in CSS?
Use background-image: url('image.jpg') with background-size: cover and background-position: center. For overlays, use background: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url('hero.jpg') — the gradient goes on top of the image.