🌈 Colors, Typography, and Basic Styling
Structure and layout get a page working; color and type make it feel designed. In this lesson you'll learn the languages CSS uses to describe color, how to make text readable and beautiful, and how to reach for custom properties, backgrounds, and shadows — the finishing touches that separate a rough draft from something you'd be proud to ship.
Week 1 · Wednesday: CSS Basics · Lecture 3
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Specify colors four ways — named, hex, RGB(A), and HSL(A) — and know when each shines
- Define reusable colors with CSS custom properties (variables)
- Control text with font family stacks, size, weight, line height, and spacing
- Load custom web fonts responsibly with Google Fonts and
@font-face - Style backgrounds and build gradients
- Add depth with box and text shadows, and judge color contrast for accessibility
Estimated Time: 65 minutes
Practice: Build a small design system — a color palette in variables, a type scale, and a styled button and card.
In This Lesson
Colors in CSS
CSS gives you several notations for the same colors — different paintbrushes for different needs. All of them ultimately describe the same red, green, and blue light your screen emits; they just spell it differently.
1. Named colors
.element {
color: red;
background-color: skyblue;
border-color: darkgreen;
}
/* 140+ keywords exist. Great for quick tests; too limited for a real palette. */
2. Hexadecimal
Two hex digits each for red, green, blue, from 00 to ff. Compact and universal — the format you'll paste from most design tools.
.element {
color: #ff0000; /* red */
color: #0000ff; /* blue */
color: #f00; /* shorthand — same as #ff0000 */
color: #ff000080; /* 8-digit hex adds alpha: 50% transparent red */
}
3. RGB / RGBA
Red, green, blue as numbers 0–255, with an optional alpha (opacity) from 0 to 1. Handy when you're thinking in channel values or need transparency.
.element {
color: rgb(255, 0, 0); /* red */
background: rgba(0, 0, 0, 0.8); /* black at 80% opacity */
}
/* Modern syntax also allows spaces and a slash for alpha: */
.modern { color: rgb(255 0 0 / 50%); }
4. HSL / HSLA
Hue (0–360° around the color wheel), Saturation (0–100%), Lightness (0–100%). This is the human-friendly one: to make a color darker, just lower the lightness; to build a palette, keep the hue and vary saturation and lightness.
.element {
color: hsl(0, 100%, 50%); /* red */
color: hsl(120, 100%, 50%); /* green */
color: hsl(240, 100%, 50%); /* blue */
color: hsla(0, 100%, 50%, 0.5);/* 50% transparent red */
}
/* Same hue, different lightness = a coherent shade ramp */
.brand-light { color: hsl(210, 90%, 70%); }
.brand { color: hsl(210, 90%, 50%); }
.brand-dark { color: hsl(210, 90%, 35%); }
💡 Which format should I use?
Use hex for fixed brand colors copied from a design tool, rgba/hsla when you need transparency, and HSL when you want to reason about and generate related shades by hand. They're interchangeable — pick the one that makes the code clearest.
Custom Properties
Rather than repeating #007bff in forty places, define it once as a custom property (often called a CSS variable) and reference it everywhere. Change the definition, and every use updates — the foundation of theming and dark mode.
:root {
--color-primary: #007bff;
--color-danger: #dc3545;
--color-text: #333333;
--space-md: 1rem;
}
.button {
background-color: var(--color-primary);
padding: var(--space-md);
}
/* Provide a fallback if the variable isn't defined */
.icon { color: var(--color-brand, #007bff); }
:root is the highest-level element (the <html>), so variables defined there are available everywhere. And because custom properties follow inheritance, you can override them for a subtree — which is exactly how a .dark-theme class can repaint an entire page.
✅ This is how dark mode works
:root { --color-bg: #fff; --color-text: #111; }
:root.dark-theme { --color-bg: #111; --color-text: #eee; }
body { background: var(--color-bg); color: var(--color-text); }
/* Toggle the .dark-theme class on <html> and the whole page flips. */
Typography Fundamentals
Typography is arranging text so it's easy — and pleasant — to read. Most of a page's "feel" comes from a handful of font properties used well.
Font family & the font stack
You list fonts in preference order. The browser uses the first one it has installed, falling back down the list, and finally to a generic family (serif, sans-serif, monospace) that's guaranteed to exist.
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
/* The "system font stack" — uses each OS's native UI font, zero download */
.native {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, "Helvetica Neue", sans-serif;
}
Font size — prefer rem
.text {
font-size: 16px; /* absolute — fixed regardless of user settings */
font-size: 1rem; /* relative to the ROOT font size (recommended) */
font-size: 1.25em; /* relative to the PARENT's font size */
}
💡 rem vs em
rem is always relative to the root (<html>) font size, so it's predictable. em is relative to the current element's font size, which compounds when nested — three levels of 1.2em multiply together. Use rem for font sizes by default; it also respects a user who bumps up their browser's base font for readability.
Font weight, style, and case
.text {
font-weight: 400; /* normal; 700 = bold; 100–900 numeric scale */
font-style: italic; /* normal | italic */
text-transform: uppercase; /* uppercase | lowercase | capitalize */
font-variant: small-caps;
}
Line height & spacing — where readability lives
.text {
line-height: 1.6; /* unitless is best — scales with font size */
letter-spacing: 0.02em; /* tracking between letters */
word-spacing: 0.1em;
}
A unitless line-height like 1.6 means "1.6 times this element's font size," and it recalculates correctly for every child. A fixed line-height: 24px would not adapt when a heading uses a larger font. For body text, 1.5–1.6 is the readable sweet spot.
Web Fonts
Custom fonts give a site its personality. You can load them from a service or host the files yourself.
Google Fonts (quickest start)
<!-- In the <head> -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
body { font-family: "Roboto", sans-serif; }
@font-face (self-hosting)
@font-face {
font-family: "BrandFont";
src: url("/fonts/brand.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap; /* show fallback text immediately, swap when loaded */
}
.brand-text { font-family: "BrandFont", sans-serif; }
⚠️ Always set font-display: swap
Without it, some browsers hide text until the custom font downloads — a flash of invisible text (FOIT). swap shows your fallback font right away and swaps in the custom one when it arrives, so content is never invisible. Keep custom font files small (use woff2) and load only the weights you actually use.
Text Styling
Alignment & decoration
.text { text-align: center; } /* left | right | center | justify */
a { text-decoration: none; } /* remove the default underline */
.fancy-link {
text-decoration: underline wavy crimson;
text-underline-offset: 3px;
}
Text shadow
/* offset-x | offset-y | blur | color */
.shadow { text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); }
.glow { text-shadow: 0 0 10px #00e5ff; }
Truncating overflow text
/* Single line: cut off with an ellipsis (…) */
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Multiple lines: clamp to 3 lines then ellipsis */
.line-clamp {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
Backgrounds & Gradients
Background properties
.hero {
background-image: url("/img/hero.jpg");
background-repeat: no-repeat;
background-position: center;
background-size: cover; /* fill the box, cropping as needed */
}
/* Shorthand: color image repeat position / size */
.hero {
background: #222 url("/img/hero.jpg") no-repeat center / cover;
}
Gradients are images
A gradient is generated by CSS and used anywhere an image can go — no file needed.
.linear {
background: linear-gradient(to right, #6366f1, #06b6d4);
}
.angled {
background: linear-gradient(45deg, #f00 0%, #0f0 50%, #00f 100%);
}
.radial {
background: radial-gradient(circle, #6366f1, #0f172a);
}
/* Layer a dark overlay on top of a photo for readable text */
.overlay {
background:
linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)),
url("/img/hero.jpg") center / cover;
}
Shadows & Depth
Shadows imply elevation — they're how a card looks like it floats above the page. The syntax is offset-x, offset-y, blur, optional spread, then color.
/* x | y | blur | color */
.card { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); }
/* Layered shadows read as softer and more realistic */
.card-raised {
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.08),
0 4px 12px rgba(0, 0, 0, 0.12);
}
/* Inset shadow: carved-in look */
.well { box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.3); }
Accessibility: contrast is not optional
Pretty colors are worthless if people can't read them. The WCAG guidelines require a contrast ratio of at least 4.5:1 between normal text and its background (3:1 for large text). Light gray text on white fails this constantly.
⚠️ Check contrast before you ship
Your browser's DevTools shows a contrast ratio right in the color picker, and tools like the WebAIM Contrast Checker grade any pair. Never rely on color alone to convey meaning either — pair a red error color with an icon or text, so colorblind users get the message too.
Practice & Quiz
🏋️ Exercise 1: A palette in variables
Goal: Define a small palette as custom properties on :root, then style a .button that uses the primary color for its background and darkens on hover — changing only the variable values, not the button rule, if you later rebrand.
💡 Hint
Define --color-primary and a --color-primary-dark on :root. Reference them in .button and .button:hover with var().
✅ Solution
:root {
--color-primary: #007bff;
--color-primary-dark: #0056b3;
--radius: 6px;
}
.button {
background: var(--color-primary);
color: #fff;
padding: 0.75rem 1.5rem;
border: none;
border-radius: var(--radius);
cursor: pointer;
transition: background 0.2s ease;
}
.button:hover {
background: var(--color-primary-dark);
}
🏋️ Exercise 2: A readable type scale
Goal: Set a comfortable base for body text and a clear heading hierarchy using rem units and a unitless line height.
✅ Solution
body {
font-family: system-ui, sans-serif;
font-size: 1rem; /* 16px base */
line-height: 1.6; /* readable body leading */
color: #333;
}
h1 { font-size: 2.5rem; line-height: 1.2; }
h2 { font-size: 2rem; line-height: 1.25; }
h3 { font-size: 1.5rem; line-height: 1.3; }
p { max-width: 65ch; } /* ~65 characters per line for readability */
🎯 Quick Quiz
Question 1: Which color format makes it easiest to create a darker shade of an existing color by hand?
Question 2: Why is a unitless line-height: 1.6 preferred over line-height: 24px?
Question 3: What is the minimum WCAG contrast ratio for normal body text against its background?
Best Practices & Pitfalls
✅ Do
- Centralize colors and spacing in custom properties for easy theming
- Size text in
remand use a unitless line height - Keep body text around 16px+ with ~45–75 characters per line
- Set
font-display: swapand servewoff2to keep fonts fast - Verify contrast meets 4.5:1 before shipping
❌ Don't
- Hardcode the same hex value in dozens of rules — use a variable
- Load a dozen font families and weights; each one costs download time
- Convey meaning with color alone — add text or icons for accessibility
- Use light-gray-on-white body text that fails contrast checks
📖 A tiny design system pays off fast
:root {
--font-body: system-ui, sans-serif;
--font-heading: Georgia, serif;
--color-primary: #007bff;
--color-text: #333;
--color-bg: #fff;
--space: 1rem;
--radius: 0.5rem;
}
/* Every component references these tokens — restyle the whole site
by editing this one block. */
Summary
🎉 Key Takeaways
- Colors come in named, hex, RGB(A), and HSL(A) — HSL is easiest for hand-building shades
- Custom properties (
--name/var()) centralize your palette and power theming - Readable type =
remsizes, unitless line height, sensible line length, and a font stack with fallbacks - Load web fonts with
font-display: swapandwoff2; gradients are images CSS generates for free - Shadows imply depth, and 4.5:1 contrast keeps text accessible
📚 Additional Resources
- MDN — CSS color values
- MDN — Using CSS custom properties
- MDN — Fundamental text and font styling
- WebAIM — Contrast Checker
🚀 What's Next?
That wraps up the CSS basics — you can select, size, place, and style. Next week you'll learn to arrange whole layouts flexibly and responsively, starting with Flexbox Fundamentals.
🎉 Beautifully done!
You now have the full toolkit for making a page look intentional. Color, type, and spacing used with restraint are what "good design" actually is.