Typography, Color, and Backgrounds

Table of Contents

  1. Typography Is Most of Design
  2. Fonts and Font Stacks
  3. Web Fonts, Including Khmer
  4. Size, Weight, and Spacing
  5. Color Formats
  6. Contrast
  7. Backgrounds, Shadows, and Transitions
  8. Custom Properties
  9. Dark Mode
  10. Try It Yourself

1. Typography Is Most of Design

Most of a web page is text, so most of what makes a page look good (or bad) is how its text is set. Before reaching for gradients or effects, get four things right: the font, the size, the line spacing, and the contrast. A page with good typography and plain colors beats a colorful page with bad type every time.

2. Fonts and Font Stacks

font-family takes a stack — a list in order of preference. The browser uses the first font that's installed and falls back through the list:

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

The last item should always be a generic family:

GenericLooks like
sans-serifClean, no little feet — good for screens
serifLittle strokes at the ends — traditional, good for long reading
monospaceEvery letter the same width — for code
system-uiThe operating system's own UI font

system-ui is the quick, free option: it loads instantly because the font is already on the device, and it looks native on every platform. Names with spaces need quotes.

3. Web Fonts, Including Khmer

To use a font people don't have installed, you load it from the web. The easiest way is Google Fonts, which serves fonts from a CDN as in Caching, CDNs, and Speed. Pick a font, copy the <link> tags it gives you into the <head> before your stylesheet, and use the name in font-family:

<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&family=Noto+Sans+Khmer:wght@400;700&display=swap"
  rel="stylesheet">
body {
  font-family: "Inter", "Noto Sans Khmer", system-ui, sans-serif;
}

Why list two? A font only contains the characters its designer drew. Inter has no Khmer glyphs, so when the browser meets a Khmer letter it moves on to the next font in the stack, Noto Sans Khmer. List a font for each script you need, and Latin and Khmer mix smoothly on one page. Other Khmer web fonts are Battambang, Hanuman, and Kantumruy Pro.

Some tips:

  • The display=swap part shows text immediately in a fallback font, then swaps in the web font. Without it, text can be invisible while the font downloads.
  • Every font and weight is a download. Use one or two families and only the weights you need (usually 400 and 700).
  • Self-hosting font files from your own folder avoids a third-party request, at the cost of a bit more setup (@font-face).

4. Size, Weight, and Spacing

body {
  font-size: 1rem;      /* 16px by default; leave it alone */
  line-height: 1.6;
}

h1 { font-size: 2.25rem; }
h2 { font-size: 1.75rem; }
h3 { font-size: 1.25rem; }

The key properties:

PropertyNotes
font-sizeSet the body to 1rem. Use rem for everything else
font-weight400 normal, 600 semi-bold, 700 bold
line-heightSpace between lines. 1.5–1.7 for body text. Use a unitless number. Khmer needs a bit more, around 1.7–1.8, because of its stacked marks
letter-spacingSpace between letters; usually leave it alone
text-alignleft, center, right. Use left for long text
text-decorationUnderlines. Keep them on links in body text
max-width: 65chKeeps lines to a readable 45–75 characters

Headings need tighter line spacing than body text — line-height: 1.2 looks better. A type scale, where each heading size is a consistent step up, makes pages look planned: for example 1.25 × the previous size (1rem, 1.25rem, 1.56rem, 1.95rem).

For long-text pages such as blog posts, the single best change is:

p {
  max-width: 65ch;
}

5. Color Formats

CSS offers several ways to write a color. All can describe the same value:

FormatExampleNotes
Namedrebeccapurple, tomatoAbout 140; fun for testing
Hex#b45309Most common; red, green, blue in hex. #b45309 is the B4, 53, 09 bytes from How Computers Work
rgb()rgb(180 83 9)Same as hex in decimals; add / 50% for transparency
hsl()hsl(26 91% 37%)Hue (0–360° on a color wheel), saturation, lightness

hsl() is the easiest to adjust by hand: keep the hue and change the lightness to get a lighter or darker version of the same color. Hex is fine for copying colors from a design tool.

h1 {
  color: hsl(26 91% 37%);
  background-color: hsl(26 91% 95%);
}

Colors to know: color is the text color; background-color is the fill; border-color is the border. The keyword currentColor means "the current text color", useful for borders and icons that should match.

6. Contrast

Text has to be readable. Contrast ratio compares the lightness of the text and its background, from 1:1 (invisible) to 21:1 (black on white). The accessibility guideline (WCAG AA) asks for:

  • 4.5:1 for normal text
  • 3:1 for large text (18px+ bold or 24px+) and UI components

Light gray text on white is the most common failure. You don't need to calculate it: in DevTools, click the color swatch next to color and the picker shows the contrast ratio with a ✓ or ✗. Chrome also has Lighthouse and Firefox has an Accessibility panel for this.

And never rely on color alone to carry meaning — an error shown only in red can't be seen by a colorblind user. Add an icon or text such as "Error:".

7. Backgrounds, Shadows, and Transitions

Backgrounds do more than colors:

.hero {
  background-color: #fef3c7;
  background-image: linear-gradient(135deg, #fef3c7, #fde68a);
}

.banner {
  background-image: url("images/banner.jpg");
  background-size: cover;      /* fill the box, crop if needed */
  background-position: center;
  background-repeat: no-repeat;
}

A background image is decoration. If it carries content, use <img> with alt instead, since a CSS background has no alternative text.

Two more finishing touches:

.card {
  border-radius: 8px;
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.1);
  transition: box-shadow 0.2s, transform 0.2s;
}

.card:hover {
  box-shadow: 0 6px 16px rgb(0 0 0 / 0.15);
  transform: translateY(-2px);
}

box-shadow takes horizontal offset, vertical offset, blur, and color. transition animates the change smoothly. Keep effects subtle. Respect visitors who dislike motion with:

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}

8. Custom Properties

You've now typed #b45309 many times. If you wanted a different brand color, you'd have to find all of them. Custom properties (also called CSS variables) solve it. Define once, using a name that starts with --, then read with var():

:root {
  --color-text: #292524;
  --color-bg: #fafaf9;
  --color-accent: #b45309;
  --color-border: #d6d3d1;
  --radius: 8px;
}

body {
  color: var(--color-text);
  background-color: var(--color-bg);
}

a {
  color: var(--color-accent);
}

.card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

:root is the <html> element, so variables defined there are available everywhere. A custom property is live in the browser: you can change it in DevTools, override it for one section, or — as the next section shows — change it for a whole theme.

Use variables for colors, spacing steps, radii, and fonts. Not for everything: a value used once doesn't need a name.

9. Dark Mode

Many operating systems have a dark theme, and browsers tell you which one the user prefers through a media query:

@media (prefers-color-scheme: dark) {
  :root {
    --color-text: #e7e5e4;
    --color-bg: #1c1917;
    --color-accent: #fbbf24;
    --color-border: #44403c;
  }
}

Because every rule uses var(--…), redefining those variables is all it takes. The whole page switches without a single other change. That's the payoff for chapter 6's habit of keeping styles in one place.

Remember to recheck contrast in dark mode — the accent that worked on a cream background may not on near-black. To test, switch your OS theme, or in Chrome DevTools open the Rendering panel (Cmd/Ctrl+Shift+P → "Show Rendering") and set Emulate CSS media feature prefers-color-scheme.

Add <meta name="color-scheme" content="light dark"> in the <head> so that built-in controls such as form fields and scrollbars follow the theme too.

10. Try It Yourself

  1. Pick fonts. Add Inter (or another font you like) and Noto Sans Khmer from Google Fonts. Put a line of Khmer in your page and check in DevTools → Computed → Rendered Fonts which font drew which text.
  2. Set the type. Give the body a line-height of 1.6, headings 1.2, and paragraphs max-width: 65ch. Compare the page before and after.
  3. Convert to variables. Replace every repeated color in style.css with a custom property. Change --color-accent and watch the whole page update.
  4. Check contrast. Use DevTools' color picker on your body text, links, and button text. Fix anything below 4.5:1.
  5. Add dark mode. Add the prefers-color-scheme block, and test it with the Rendering panel. Check the form fields and the table.
  6. Add polish. Give .card a shadow and a hover transition, and the reduced-motion rule. Commit.

Your page now has structure and style. The next two chapters are about layout — arranging boxes in rows and grids.

Buy Me a Coffee

Found this tutorial helpful? Consider buying me a coffee to support more free content!