Font Properties and Web Fonts

โ–ถ Try It Yourself

Typography is the single most influential design decision on a webpage. Approximately 95% of the web is text, and how that text looks โ€” its typeface, size, weight, and spacing โ€” determines whether a site feels professional, playful, trustworthy, or dated. In this lesson you will master the CSS font properties and learn to load custom web fonts with @font-face and Google Fonts โ€” the foundation of all CSS typography work.

Core Font Properties

Property Controls Common Values
font-family Typeface โ€” with fallback stack 'Inter', system-ui, sans-serif
font-size Text size โ€” relative or absolute 1rem, 16px, clamp(1rem, 2.5vw, 1.5rem)
font-weight Stroke thickness 400 (normal), 700 (bold), 100โ€“900
font-style Upright vs slanted normal, italic, oblique
font-variant Small caps and OpenType features small-caps, normal
font Shorthand for all font properties 700 1.25rem/1.4 'Inter', sans-serif

Font Size Units Compared

Unit Relative To Best For
px Absolute pixels Borders, shadows โ€” not recommended for font-size
rem Root element (html) font-size Body text, headings โ€” scales with user browser settings
em Parent element font-size Component-relative sizing โ€” padding, margin inside components
% Parent element font-size Similar to em โ€” less common for font-size
vw Viewport width Fluid headings โ€” usually combined with clamp()
clamp(min, val, max) Fluid between min and max Responsive type that scales between two size limits

Loading Web Fonts

Method How Pros / Cons
Google Fonts (link) <link> in HTML head Simplest; CDN-hosted; privacy considerations
Google Fonts (CSS import) @import url(...) in CSS Easy but adds a render-blocking request
@font-face Self-host font files Full control; best performance; GDPR-friendly
System font stack No loading โ€” use OS fonts Instant; no FOUT; less branding control
Note: Always declare font-family as a stack with multiple fallbacks: 'Inter', system-ui, -apple-system, sans-serif. If Inter fails to load (network error, ad-blocker), the browser moves to the next font in the stack. Without fallbacks, users see the browser’s default serif font โ€” usually Times New Roman.
Tip: Set your base font size on html using a percentage โ€” html { font-size: 100%; } โ€” never a fixed pixel value. This respects the user’s browser font-size preference. Then use rem throughout your stylesheet for all text sizes. If a user has set their browser to 20px base, all your rem values scale up proportionally.
Warning: Avoid loading more than 2โ€“3 font weights from Google Fonts or any web font service. Each additional weight is an extra HTTP request and extra kilobytes. A common mistake is loading all weights (100 through 900) when only 400 and 700 are used. Specify only what you need: weights=400;700.

Basic Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Font Properties and Web Fonts</title>

  <!-- Google Fonts: two weights only -->
  <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;700&family=Playfair+Display:ital,wght@0,700;1,700&display=swap" rel="stylesheet">

  <style>
    /* Base โ€” respects browser font-size preference */
    html { font-size: 100%; }

    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    body {
      font-family: 'Inter', system-ui, -apple-system, sans-serif;
      font-size: 1rem;         /* 16px at browser default */
      font-weight: 400;
      line-height: 1.6;
      color: #1e293b;
      background: #f8fafc;
      padding: 40px 24px;
      max-width: 680px;
      margin: 0 auto;
    }

    /* Display heading โ€” Playfair Display, bold */
    .display-heading {
      font-family: 'Playfair Display', Georgia, serif;
      font-size: clamp(2rem, 5vw, 3.5rem); /* fluid: 32pxโ€“56px */
      font-weight: 700;
      line-height: 1.1;
      letter-spacing: -0.02em;
      color: #0f172a;
      margin-bottom: 16px;
    }

    /* Italic display variant */
    .display-heading em {
      font-style: italic;
      color: #4f46e5;
    }

    /* Section heading โ€” Inter, semibold */
    .section-heading {
      font-family: 'Inter', system-ui, sans-serif;
      font-size: 1.25rem;      /* 20px */
      font-weight: 700;
      letter-spacing: -0.01em;
      color: #0f172a;
      margin: 32px 0 12px;
    }

    /* Body paragraph */
    p {
      font-size: 1rem;
      line-height: 1.75;
      color: #475569;
      margin-bottom: 16px;
    }

    /* Small label / caption */
    .label {
      font-size: 0.75rem;      /* 12px */
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 0.08em;
      color: #64748b;
    }

    /* Weight demo */
    .weight-demo { display: flex; gap: 24px; flex-wrap: wrap; margin-bottom: 24px; }
    .weight-item { font-size: 1rem; color: #0f172a; }

    /* @font-face self-hosting pattern (shown as code) */
    .code-block {
      background: #1e293b;
      color: #e2e8f0;
      padding: 20px 24px;
      border-radius: 10px;
      font-family: 'Courier New', monospace;
      font-size: 0.825rem;
      line-height: 1.7;
      overflow-x: auto;
      white-space: pre;
      margin-bottom: 24px;
    }

    /* font shorthand demo */
    .shorthand-demo {
      /* weight size/line-height family */
      font: 700 1.125rem/1.4 'Playfair Display', serif;
      color: #0f172a;
      margin-bottom: 8px;
    }
  </style>
</head>
<body>

  <p class="label">Display Heading โ€” Playfair Display + clamp()</p>
  <h1 class="display-heading">Beautiful <em>Typography</em> Starts Here</h1>

  <p>This body text uses Inter at 1rem (16px) with a line-height of 1.75 โ€” comfortable for reading long-form content. The colour is a dark slate rather than pure black, which reduces eye strain.</p>

  <h2 class="section-heading">Font Weight Scale</h2>
  <div class="weight-demo">
    <span class="weight-item" style="font-weight:300">300 Light</span>
    <span class="weight-item" style="font-weight:400">400 Regular</span>
    <span class="weight-item" style="font-weight:500">500 Medium</span>
    <span class="weight-item" style="font-weight:600">600 SemiBold</span>
    <span class="weight-item" style="font-weight:700">700 Bold</span>
    <span class="weight-item" style="font-weight:800">800 ExtraBold</span>
  </div>

  <h2 class="section-heading">Self-Hosting with @font-face</h2>
  <div class="code-block">@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap; /* show fallback while loading */
}

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}</div>

  <h2 class="section-heading">font Shorthand</h2>
  <p class="shorthand-demo">font: 700 1.125rem/1.4 'Playfair Display', serif</p>
  <p style="font-size:0.875rem;color:#64748b">Shorthand order: style? variant? weight? size/line-height family</p>

</body>
</html>

How It Works

Step 1 โ€” html font-size: 100% Enables User Scaling

Setting font-size: 100% on html means the base size equals whatever the user has set in their browser preferences (typically 16px). If they set 20px, all rem values scale proportionally. Setting html { font-size: 16px } would override and ignore this preference โ€” an accessibility issue.

Step 2 โ€” clamp() Makes Headings Fluid

clamp(2rem, 5vw, 3.5rem) creates a heading that is at least 32px, scales with the viewport (5% of viewport width), and never exceeds 56px. On a 320px phone: 5vw = 16px, but clamp holds it at 32px min. On a 1400px monitor: 5vw = 70px, but clamp caps it at 56px.

Step 3 โ€” The Font Stack Provides Fallbacks

If Inter fails to load, system-ui selects the OS’s default UI font (San Francisco on macOS, Segoe UI on Windows). -apple-system provides an older Safari fallback. sans-serif is the final generic fallback that every browser must support.

Step 4 โ€” @font-face Gives Full Control

Self-hosting with @font-face keeps font requests on your own domain, eliminating third-party GDPR concerns. font-display: swap tells the browser to show the fallback font immediately and swap to the web font when it loads โ€” preventing invisible text during load.

Step 5 โ€” font Shorthand Consolidates Declarations

The font shorthand must include at minimum font-size and font-family โ€” omitting either invalidates the entire rule. Optional values before size: font-style, font-variant, font-weight. Line-height follows size separated by a slash: 1rem/1.6.

Real-World Example: Design System Type Scale

/* type-scale.css */
:root {
  /* Base */
  --font-sans:   'Inter', system-ui, -apple-system, sans-serif;
  --font-serif:  'Playfair Display', Georgia, serif;
  --font-mono:   'JetBrains Mono', 'Courier New', monospace;

  /* Type scale โ€” Major Third (1.25x) */
  --text-xs:   0.64rem;   /*  ~10px */
  --text-sm:   0.8rem;    /*  ~13px */
  --text-base: 1rem;      /*   16px */
  --text-lg:   1.25rem;   /*   20px */
  --text-xl:   1.563rem;  /*   25px */
  --text-2xl:  1.953rem;  /*   31px */
  --text-3xl:  2.441rem;  /*   39px */
  --text-4xl:  3.052rem;  /*   49px */

  /* Weights */
  --weight-normal:   400;
  --weight-medium:   500;
  --weight-semibold: 600;
  --weight-bold:     700;
  --weight-extrabold:800;

  /* Line heights */
  --leading-tight:  1.2;
  --leading-snug:   1.375;
  --leading-normal: 1.5;
  --leading-relaxed:1.625;
  --leading-loose:  1.75;
}

/* Utility classes */
.text-xs   { font-size: var(--text-xs); }
.text-sm   { font-size: var(--text-sm); }
.text-base { font-size: var(--text-base); }
.text-lg   { font-size: var(--text-lg); }
.text-xl   { font-size: var(--text-xl); }
.text-2xl  { font-size: var(--text-2xl); }
.text-3xl  { font-size: var(--text-3xl); }
.text-4xl  { font-size: var(--text-4xl); }

.font-normal   { font-weight: var(--weight-normal); }
.font-medium   { font-weight: var(--weight-medium); }
.font-semibold { font-weight: var(--weight-semibold); }
.font-bold     { font-weight: var(--weight-bold); }

/* Heading styles */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-sans);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  color: #0f172a;
  letter-spacing: -0.02em;
}
h1 { font-size: clamp(var(--text-2xl), 5vw, var(--text-4xl)); }
h2 { font-size: clamp(var(--text-xl), 3vw, var(--text-3xl)); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }

p, li { font-size: var(--text-base); line-height: var(--leading-relaxed); }
small { font-size: var(--text-sm); }
code  { font-family: var(--font-mono); font-size: 0.9em; }

Common Mistakes

Mistake 1 โ€” Setting html font-size in px

โŒ Wrong โ€” overrides the user’s accessibility font preference:

html { font-size: 16px; } /* ignores browser/user font-size setting */

โœ… Correct โ€” use percentage to respect user preferences:

html { font-size: 100%; } /* 100% of whatever the user has set */

Mistake 2 โ€” Loading all font weights

โŒ Wrong โ€” loads 9 weights, most unused โ€” heavy network cost:

<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">

โœ… Correct โ€” load only what you actually use:

<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">

Mistake 3 โ€” Using em for font-size on nested elements

โŒ Wrong โ€” em compounds: a 0.875em element inside another 0.875em element = 0.766em:

.card        { font-size: 0.875em; }
.card .label { font-size: 0.875em; } /* compounds to ~12px โ€” unexpectedly small */

โœ… Correct โ€” use rem for font-size to always be relative to root:

.card        { font-size: 0.875rem; }
.card .label { font-size: 0.75rem; }  /* always relative to html โ€” predictable */

▶ Try It Yourself

Quick Reference

Property Values Notes
font-family 'Inter', system-ui, sans-serif Always include fallbacks
font-size 1rem, clamp(1rem, 3vw, 2rem) Use rem not px for accessibility
font-weight 400, 700, 100โ€“900 Numeric values โ€” no “bold” keyword in design systems
font-style normal, italic Use semantic <em> for meaning, CSS for decoration
font-display swap, block, fallback In @font-face โ€” swap prevents invisible text
clamp(min, val, max) clamp(1rem, 4vw, 2.5rem) Fluid type โ€” scales with viewport
@font-face Self-hosted woff2 files Best performance and privacy

🧠 Test Yourself

A user has set their browser default font size to 20px. You have html { font-size: 100%; } and p { font-size: 1rem; }. How large will paragraph text render?





โ–ถ Try It Yourself