Skip to main content

🎨 Lesson 11: Colors & Typography

You've connected a stylesheet and written your first CSS rules. Now it's time to go deeper into the two things that have the biggest impact on how a page feels: color and type. The right palette and font choices can make a plain page look professional — or playful, or elegant, or bold.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Use named colors, hex codes, RGB, and HSL to set any color in CSS
  • Apply color to text, backgrounds, and borders
  • Build a font stack with fallback fonts
  • Add Google Fonts to your website
  • Control font size, weight, style, and line height
  • Use rem and em units for scalable typography
  • Create a cohesive color and type system for your project

Estimated Time: 45 minutes

Hands-on: You'll build a color palette and typography system for your website.

📑 In This Lesson

CSS Color Formats

CSS gives you several ways to express a color. They all produce the same result — it's just a matter of which notation you prefer and what you need.

Named Colors

CSS recognizes 147 named colors — simple English words like red, navy, tomato, cornflowerblue, and papayawhip:

h1 {
    color: navy;
    background-color: papayawhip;
}

Named colors are great for quick prototyping and learning, but they're limited — you can't express every shade. For precise control, you need one of the formats below.

Hex Codes

The most common color format on the web. A hex code starts with # followed by six characters (0–9 and A–F), representing the red, green, and blue channels:

h1 {
    color: #1a365d;        /* dark blue */
    background-color: #f0f4f8;  /* light gray-blue */
    border-color: #3b82f6;      /* bright blue */
}

The format is #RRGGBB — two hex digits per channel. Each channel ranges from 00 (none) to FF (maximum). Some common patterns:

  • #000000 — black (all channels off)
  • #ffffff — white (all channels full)
  • #ff0000 — pure red
  • #00ff00 — pure green
  • #0000ff — pure blue

If both digits in each pair are the same, you can use a shorthand: #aabbcc can be written as #abc. So #ff6600 becomes #f60.

RGB

The rgb() function uses decimal numbers (0–255) for red, green, and blue:

h1 {
    color: rgb(26, 54, 93);           /* same as #1a365d */
    background-color: rgb(240, 244, 248);  /* same as #f0f4f8 */
}

RGB and hex express the same thing in different notation. Use whichever you find more readable.

RGBA (with Transparency)

Add an alpha channel (0 to 1) to make a color semi-transparent:

.overlay {
    background-color: rgba(0, 0, 0, 0.5);  /* 50% transparent black */
}

.glass-panel {
    background-color: rgba(255, 255, 255, 0.8);  /* 80% opaque white */
}

An alpha of 0 is fully transparent; 1 is fully opaque.

HSL (Hue, Saturation, Lightness)

HSL is the most human-friendly format. Instead of mixing red/green/blue, you think in terms of:

  • Hue — the color itself, as a degree on a color wheel (0°=red, 120°=green, 240°=blue)
  • Saturation — how vivid the color is (0%=gray, 100%=full color)
  • Lightness — how light or dark (0%=black, 50%=normal, 100%=white)
h1 {
    color: hsl(217, 56%, 23%);    /* dark blue */
}

.accent {
    color: hsl(217, 91%, 60%);    /* bright blue — same hue, more saturated, lighter */
}

HSL shines when building color palettes — you can keep the same hue and just adjust saturation and lightness to create harmonious variations.

graph LR A["Color Formats"] --> B["Named
navy, tomato
Easy but limited"] A --> C["Hex
#1a365d
Most common"] A --> D["RGB/RGBA
rgb(26, 54, 93)
Decimal + transparency"] A --> E["HSL/HSLA
hsl(217, 56%, 23%)
Most intuitive"] style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style B fill:#fffbeb,stroke:#f59e0b,stroke-width:1px,color:#1e293b style C fill:#f0fdf4,stroke:#22c55e,stroke-width:1px,color:#1e293b style D fill:#fdf4ff,stroke:#a855f7,stroke-width:1px,color:#1e293b style E fill:#fef2f2,stroke:#ef4444,stroke-width:1px,color:#1e293b

💡 Which Format Should I Use?

Hex is the industry standard — you'll see it everywhere. HSL is best when you're designing a palette and want to create lighter/darker variations of the same color. RGBA/HSLA whenever you need transparency. Named colors for quick experiments. Pick one format for your project and stay consistent.

Applying Color to Elements

CSS has several properties that accept color values. Here are the most common ones:

color — Text Color

p {
    color: #333333;  /* dark gray text */
}

.error {
    color: #dc2626;  /* red text for error messages */
}

background-color — Background

body {
    background-color: #f8fafc;  /* light background for the whole page */
}

.warning-box {
    background-color: #fef3c7;  /* soft yellow background */
    padding: 15px;
}

border-color — Borders

/* You can set border-color separately... */
.card {
    border: 2px solid;
    border-color: #e2e8f0;
}

/* ...or all at once in the border shorthand */
.card-accent {
    border-left: 4px solid #3b82f6;
}

Putting It Together

/* A styled card using multiple color properties */
.info-card {
    color: #1e293b;
    background-color: #eff6ff;
    border: 1px solid #bfdbfe;
    border-left: 4px solid #3b82f6;
    padding: 20px;
}

💡 Contrast Matters

Always make sure your text color has enough contrast against its background. Light gray text on a white background might look sleek, but it's hard to read — especially for people with low vision. The Web Content Accessibility Guidelines (WCAG) recommend a contrast ratio of at least 4.5:1 for normal text. Free tools like WebAIM's Contrast Checker let you test your combinations instantly.

Font Families and Font Stacks

The font-family property controls which typeface the browser uses for text. But here's the catch: the font has to be available on the user's device. If you specify a font they don't have, the browser falls back to something else.

That's why we use font stacks — a comma-separated list of fonts, in order of preference:

body {
    font-family: "Segoe UI", Roboto, Arial, sans-serif;
}

The browser tries each font in order: first "Segoe UI" (common on Windows), then Roboto (common on Android), then Arial (nearly universal), and finally any sans-serif font as a last resort.

The Five Generic Font Families

Every font stack should end with a generic family — a keyword that tells the browser "use any font of this type":

Generic Font Families
Generic Style Common Examples Best For
serif Has small strokes (serifs) at letter ends Times New Roman, Georgia Articles, formal content
sans-serif Clean, no serifs Arial, Helvetica, Segoe UI UI, headings, modern sites
monospace Every character is the same width Courier New, Consolas Code, technical content
cursive Handwriting-like Comic Sans MS, Brush Script Decorative, sparingly
fantasy Decorative, stylized Impact, Papyrus Decorative, very sparingly

Common Professional Font Stacks

/* Clean modern sans-serif */
body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
                 Roboto, Oxygen, Ubuntu, sans-serif;
}

/* Elegant serif for articles */
.article-body {
    font-family: Georgia, "Times New Roman", Times, serif;
}

/* Code blocks */
code, pre {
    font-family: "Fira Code", Consolas, "Courier New", monospace;
}

💡 System Font Stack

The stack -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif is called the system font stack. It uses each operating system's native UI font — San Francisco on Mac/iOS, Segoe UI on Windows, Roboto on Android. This makes your site feel native on every platform, and the fonts load instantly because they're already on the device.

Adding Google Fonts

System fonts are fast and reliable, but sometimes you want a specific typeface. Google Fonts is a free library of over 1,500 web fonts that anyone can use.

Step 1: Choose a Font

  1. Go to fonts.google.com
  2. Browse or search for a font (try "Inter", "Poppins", or "Lora")
  3. Click the font name to view it
  4. Click "Get font" → "Get embed code"
  5. Copy the <link> tags provided

Step 2: Add the Link to Your HTML

Paste the link tags into your <head>, before your own stylesheet:

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Website</title>

    <!-- Google Fonts — load BEFORE your CSS -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
          rel="stylesheet">

    <!-- Your stylesheet -->
    <link rel="stylesheet" href="css/style.css">
</head>

Step 3: Use It in Your CSS

body {
    font-family: "Inter", -apple-system, BlinkMacSystemFont,
                 "Segoe UI", sans-serif;
}

Notice we still include fallback fonts after "Inter" — if Google Fonts fails to load (slow connection, firewall, etc.), the browser gracefully falls back to system fonts instead of showing a blank.

graph TD A["Google Fonts Workflow"] --> B["1. Choose font at
fonts.google.com"] B --> C["2. Copy <link> tags
into HTML <head>"] C --> D["3. Use font-family
in your CSS"] D --> E["4. Always include
fallback fonts"] style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style B fill:#f0fdf4,stroke:#22c55e,stroke-width:1px,color:#1e293b style C fill:#f0fdf4,stroke:#22c55e,stroke-width:1px,color:#1e293b style D fill:#f0fdf4,stroke:#22c55e,stroke-width:1px,color:#1e293b style E fill:#fdf4ff,stroke:#a855f7,stroke-width:1px,color:#1e293b

💡 Font Performance Tip

Every Google Font you add is an extra download. Stick to one or two fonts max — one for headings, one for body text. Loading five different fonts will slow your page down. Also, only request the weights you actually use (like 400 and 700) instead of loading every weight.

Font Sizing: px, em, and rem

The font-size property controls how big text appears. CSS offers several units, but three are essential:

px — Pixels

An absolute unit. 16px means 16 pixels, period.

h1 { font-size: 36px; }
p  { font-size: 16px; }

Pixels are predictable and easy to understand — great for learning. But they don't scale when users change their browser's default font size (an accessibility concern).

rem — Root em (Recommended)

A relative unit based on the root element's (<html>) font size. By default, browsers set the root font size to 16px, so:

  • 1rem = 16px
  • 1.5rem = 24px
  • 2rem = 32px
  • 0.875rem = 14px
h1 { font-size: 2.25rem; }    /* 36px at default root */
h2 { font-size: 1.75rem; }    /* 28px */
p  { font-size: 1rem; }       /* 16px */
small { font-size: 0.875rem; } /* 14px */

Why rem is better: If a user sets their browser's base font size to 20px (for readability), all your rem-based text scales proportionally. With px, it stays stubbornly at 36px.

em — Relative to Parent

Like rem, but relative to the parent element's font size, not the root. This can cause compounding:

.parent { font-size: 20px; }
.child  { font-size: 1.5em; }  /* 30px (1.5 × 20) */
.grandchild { font-size: 1.5em; } /* 45px (1.5 × 30) — it compounds! */

Because of compounding, em is best reserved for local adjustments (padding, margins relative to text size) rather than font sizes. Use rem for font sizes.

Font Size Units Comparison
Unit Relative To Scales with User Settings? Best For
px Nothing (absolute) No Learning, borders, shadows
rem Root element font size Yes ✅ Font sizes, spacing
em Parent element font size Yes (but compounds) Local padding/margins

Other Text Properties

Beyond font family and size, CSS gives you fine-grained control over how text looks:

font-weight — Boldness

p       { font-weight: 400; }    /* normal — same as "normal" */
strong  { font-weight: 700; }    /* bold — same as "bold" */
h1      { font-weight: 600; }    /* semi-bold (if the font supports it) */

Weight values range from 100 (thin) to 900 (black). The most common are 400 (normal) and 700 (bold). Use named values (normal, bold) or numbers — they mean the same thing.

font-style — Italic

.caption    { font-style: italic; }
.correction { font-style: normal; }  /* remove italic from em tags, for example */

line-height — Vertical Spacing

Controls the space between lines of text. This is one of the biggest readability improvements you can make:

body {
    line-height: 1.6;  /* 1.6 times the font size — great for body text */
}

h1 {
    line-height: 1.2;  /* tighter for large headings */
}

A unitless number (like 1.6) is preferred over a fixed value — it scales with the font size automatically. The default is about 1.2, which is cramped for body text. Bump it to 1.51.8 for comfortable reading.

text-align — Horizontal Alignment

h1      { text-align: center; }
p       { text-align: left; }      /* default */
.price  { text-align: right; }
.justify { text-align: justify; }  /* stretches to fill the line — use sparingly */

text-decoration — Underlines and More

a       { text-decoration: none; }           /* remove default underline */
a:hover { text-decoration: underline; }      /* add it back on hover */
del     { text-decoration: line-through; }   /* strikethrough */

text-transform — Case Changes

.uppercase { text-transform: uppercase; }    /* ALL CAPS */
.capitalize { text-transform: capitalize; }  /* First Letter Capitalized */
.lowercase { text-transform: lowercase; }    /* all lowercase */

letter-spacing and word-spacing

.spaced-heading {
    letter-spacing: 2px;   /* space between characters */
    text-transform: uppercase;  /* often paired with uppercase text */
}

.wide-text {
    word-spacing: 4px;     /* space between words */
}

💡 A Professional Typography Recipe

Here's a quick formula that instantly improves almost any page:

body {
    font-family: "Inter", system-ui, sans-serif;
    font-size: 1rem;           /* 16px base */
    line-height: 1.6;          /* comfortable reading */
    color: #1e293b;            /* near-black, not pure black */
}

h1, h2, h3 {
    line-height: 1.2;          /* tighter for headings */
    color: #0f172a;            /* slightly darker than body text */
}

h1 { font-size: 2.25rem; }    /* 36px */
h2 { font-size: 1.75rem; }    /* 28px */
h3 { font-size: 1.375rem; }   /* 22px */

Why #1e293b instead of pure #000000? Pure black on white creates harsh contrast that causes eye strain. A very dark gray is easier to read and looks more sophisticated.

Hands-on Exercise

🏋️ Exercise: Build a Color & Type System

Objective: Create a cohesive color palette and typography system in your CSS file.

Instructions:

  1. Open your css/style.css file
  2. At the top of your file, add CSS custom properties (variables) for your color palette:
    :root {
        --color-primary: #1a365d;
        --color-accent: #3b82f6;
        --color-text: #1e293b;
        --color-text-light: #64748b;
        --color-bg: #f8fafc;
        --color-bg-alt: #eff6ff;
        --color-border: #e2e8f0;
    }
  3. Replace all hard-coded color values in your CSS with these variables:
    body {
        color: var(--color-text);
        background-color: var(--color-bg);
    }
    
    h1 {
        color: var(--color-primary);
        border-bottom: 3px solid var(--color-accent);
    }
    
    a {
        color: var(--color-accent);
    }
  4. Add a Google Font (try "Inter" or "Poppins") and update your font-family
  5. Switch all font-size values from px to rem
  6. Set line-height: 1.6 on the body
  7. Make headings use line-height: 1.2
  8. Test by changing just the --color-accent variable — every accent color on the page should update at once
💡 Hint

CSS custom properties (also called CSS variables) are declared inside a :root selector and used with var(--name). The :root selector targets the <html> element, making variables available everywhere. This is a sneak preview of a powerful technique — you'll see it used extensively in professional CSS.

✅ Example Solution
/* css/style.css */

/* ===========================
   Color Palette (CSS Variables)
   =========================== */
:root {
    --color-primary: #1a365d;
    --color-accent: #3b82f6;
    --color-accent-hover: #1d4ed8;
    --color-text: #1e293b;
    --color-text-light: #64748b;
    --color-bg: #f8fafc;
    --color-bg-alt: #eff6ff;
    --color-border: #e2e8f0;
    --color-success: #22c55e;
    --color-warning: #f59e0b;
    --color-error: #dc2626;
}

/* ===========================
   Typography
   =========================== */
body {
    font-family: "Inter", -apple-system, BlinkMacSystemFont,
                 "Segoe UI", Roboto, sans-serif;
    font-size: 1rem;
    line-height: 1.6;
    color: var(--color-text);
    background-color: var(--color-bg);
    margin: 0;
    padding: 20px;
}

h1, h2, h3, h4 {
    line-height: 1.2;
    color: var(--color-primary);
}

h1 {
    font-size: 2.25rem;
    border-bottom: 3px solid var(--color-accent);
    padding-bottom: 0.5rem;
}

h2 {
    font-size: 1.75rem;
    margin-top: 2rem;
}

h3 {
    font-size: 1.375rem;
}

/* ===========================
   Links
   =========================== */
a {
    color: var(--color-accent);
    text-decoration: none;
}

a:hover {
    color: var(--color-accent-hover);
    text-decoration: underline;
}

/* ===========================
   Utility Classes
   =========================== */
.text-small {
    font-size: 0.875rem;
    color: var(--color-text-light);
}

.text-center {
    text-align: center;
}

.highlight {
    background-color: #fef08a;
    padding: 2px 6px;
    border-radius: 3px;
}

/* ===========================
   Semantic Sections
   =========================== */
header {
    background-color: var(--color-primary);
    color: white;
    padding: 1.25rem;
    margin: -20px -20px 1.25rem -20px;
}

header a {
    color: #93c5fd;
}

footer {
    margin-top: 2.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--color-border);
    color: var(--color-text-light);
    font-size: 0.875rem;
}

aside {
    background-color: var(--color-bg-alt);
    padding: 1rem;
    border-left: 4px solid var(--color-accent);
    margin: 1.25rem 0;
}

/* ===========================
   Code
   =========================== */
code {
    font-family: "Fira Code", Consolas, "Courier New", monospace;
    background-color: var(--color-bg-alt);
    padding: 2px 6px;
    border-radius: 3px;
    font-size: 0.9em;
}

🎯 Quick Quiz

Question 1: What does the hex code #ff0000 represent?

Question 2: Which unit is best for font sizes?

Question 3: Why do we use a font stack instead of a single font name?

Question 4: What's a good line-height for body text?

Question 5: In HSL, what does the "H" represent?

Summary

🎉 Key Takeaways

  • CSS colors come in four formats: named colors, hex (#1a365d), RGB/RGBA (rgb(26, 54, 93)), and HSL/HSLA (hsl(217, 56%, 23%))
  • Apply color with color (text), background-color (backgrounds), and border-color (borders)
  • Use font stacks with fallbacks: font-family: "Inter", system-ui, sans-serif;
  • Google Fonts gives you free access to 1,500+ typefaces — add via <link> in <head>
  • Use rem for font sizes (scales with user settings) and a unitless line-height of 1.5–1.8 for body text
  • CSS custom properties (--color-accent: #3b82f6;) let you define a palette once and reuse it everywhere
  • Use near-black text (#1e293b) instead of pure black for better readability

📁 Your Project So Far

my-website/
├── css/
│   └── style.css      ← now with colors, fonts, and variables
├── images/
│   └── sunset.jpg
├── index.html         ← linked to Google Fonts + CSS
├── about.html
├── recipe.html
└── contact.html

🚀 What's Next?

Your pages have beautiful colors and professional typography — but everything is just stacking top to bottom. Text bumps right up against the edges, images ignore their surroundings, and nothing has breathing room. In the next lesson, you'll learn the CSS Box Model — the foundation of how every element takes up space on the page. Understanding margin, padding, border, and content will give you precise control over spacing and layout.