Typography, Color, and Backgrounds
Table of Contents
- Typography Is Most of Design
- Fonts and Font Stacks
- Web Fonts, Including Khmer
- Size, Weight, and Spacing
- Color Formats
- Contrast
- Backgrounds, Shadows, and Transitions
- Custom Properties
- Dark Mode
- 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:
| Generic | Looks like |
|---|---|
sans-serif | Clean, no little feet — good for screens |
serif | Little strokes at the ends — traditional, good for long reading |
monospace | Every letter the same width — for code |
system-ui | The 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=swappart 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:
| Property | Notes |
|---|---|
font-size | Set the body to 1rem. Use rem for everything else |
font-weight | 400 normal, 600 semi-bold, 700 bold |
line-height | Space 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-spacing | Space between letters; usually leave it alone |
text-align | left, center, right. Use left for long text |
text-decoration | Underlines. Keep them on links in body text |
max-width: 65ch | Keeps 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:
| Format | Example | Notes |
|---|---|---|
| Named | rebeccapurple, tomato | About 140; fun for testing |
| Hex | #b45309 | Most 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
- 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.
- Set the type. Give the body a
line-heightof 1.6, headings1.2, and paragraphsmax-width: 65ch. Compare the page before and after. - Convert to variables. Replace every repeated color in
style.csswith a custom property. Change--color-accentand watch the whole page update. - Check contrast. Use DevTools' color picker on your body text, links, and button text. Fix anything below 4.5:1.
- Add dark mode. Add the
prefers-color-schemeblock, and test it with the Rendering panel. Check the form fields and the table. - Add polish. Give
.carda 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!