1Typography and Colors
▶
Typography and color are two of the most powerful tools in a web designer's toolkit. Long before a visitor reads a single word, the visual weight of your text and the palette of your page communicate tone, hierarchy, and trustworthiness. CSS provides a rich set of properties that let you control every aspect of how text appears — its shape, size, spacing, and color — and how color is applied to any element on the page. Mastering these properties means you can make content easier to read, guide the eye to what matters most, and ensure your designs look consistent and professional across all devices and screen sizes.
Font Family
The font-family property tells the browser which typeface to use when rendering text. Because not every font is installed on every device, CSS uses the concept of a font stack — an ordered, comma-separated list of font names that the browser works through from left to right until it finds one that is available on the user's system. If the first font is not found, the browser tries the second, then the third, and so on. The last entry in any font stack should always be a generic family name so that the browser is guaranteed to find something usable even in the worst case.
The standard generic family names are:
- serif — fonts with small decorative strokes at the ends of letterforms (e.g., Times New Roman, Georgia)
- sans-serif — fonts without those strokes, giving a cleaner, more modern look (e.g., Arial, Helvetica)
- monospace — fonts in which every character occupies the same width, ideal for code (e.g., Courier New, Consolas)
- cursive — fonts that mimic handwriting
- fantasy — decorative or display fonts
Web-safe fonts are typefaces that are pre-installed on the vast majority of operating systems — Windows, macOS, iOS, and Android alike. Common examples include Arial, Verdana, Georgia, and Courier New. Using them means your page will look essentially the same for almost every visitor without requiring any extra downloads. The trade-off is limited variety.
When you need a typeface beyond the web-safe set, you have two main options. Google Fonts is a free hosted library of hundreds of open-source typefaces. You link to a stylesheet in your HTML <head> and then reference the font by name in your CSS. The @font-face rule is the lower-level alternative: you host the font files yourself and declare them in CSS, giving you full control over loading, subsetting, and licensing.
/* A well-constructed font stack */
body {
font-family: "Segoe UI", Helvetica, Arial, sans-serif;
}
/* Loading a Google Font — add this link in your HTML head:
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
*/
h1 {
font-family: "Roboto", Arial, sans-serif;
}
/* Self-hosted font with @font-face */
@font-face {
font-family: "MyBrandFont";
src: url("fonts/mybrandfont.woff2") format("woff2"),
url("fonts/mybrandfont.woff") format("woff");
}
p {
font-family: "MyBrandFont", Georgia, serif;
}
Notice that every declaration above ends with a generic family. If "Segoe UI" is unavailable and Helvetica is also missing, the browser will at least fall back to Arial, and if that too is absent it will use whatever the system's default sans-serif font happens to be. Never omit that safety net.
Font Size and Weight
The font-size property controls how large or small characters appear. CSS offers both absolute and relative units:
| Unit | Type | Description | Example |
|---|---|---|---|
px |
Absolute | Device pixels — predictable but does not scale with user preferences | font-size: 16px; |
pt |
Absolute | Points (1pt = 1/72 inch) — common in print; less useful on screen | font-size: 12pt; |
em |
Relative | Relative to the font size of the parent element — can compound when nested | font-size: 1.25em; |
rem |
Relative | Relative to the font size of the root (<html>) element — consistent and predictable |
font-size: 1.5rem; |
% |
Relative | Percentage of the parent element's font size | font-size: 120%; |
The rem unit is particularly valuable for building scalable layouts. By setting a base size on the html element — commonly 16px — every rem value across the entire page is calculated relative to that single root. If you later decide the overall text should be slightly larger, you change just one value and every element that uses rem scales proportionally.
html {
font-size: 16px; /* 1rem = 16px throughout the page */
}
body {
font-size: 1rem; /* 16px */
}
h1 {
font-size: 2.5rem; /* 40px */
}
h2 {
font-size: 1.75rem; /* 28px */
}
small {
font-size: 0.75rem; /* 12px */
}
The font-weight property controls the thickness (boldness) of characters. It accepts descriptive keywords or numeric values in steps of 100:
| Value | Keyword Equivalent | Typical Use |
|---|---|---|
100 |
Thin | Very light display text |
300 |
Light | Elegant body text in certain designs |
400 |
normal |
Default body text |
700 |
bold |
Headings, labels, call-to-action text |
900 |
Black / Extra Bold | Heavy display headings |
Numeric weights only render as visually distinct steps if the loaded font actually provides a corresponding weight variant. If a font only ships with Regular (400) and Bold (700), the browser will synthesize intermediate weights, which often looks less refined than a true variable or multi-weight font.
h1 {
font-size: 2rem;
font-weight: 700; /* same as bold */
}
p {
font-size: 1rem;
font-weight: 400; /* same as normal */
}
.label {
font-weight: 600; /* semi-bold, if the font supports it */
}
Line Height and Text Alignment
The line-height property sets the vertical distance from the baseline of one line of text to the baseline of the next — effectively the breathing room between lines. It is one of the most impactful typographic settings for readability. When text is too tightly packed (line-height: 1 or less), readers struggle to track from the end of one line to the start of the next. When lines are too spread apart, the text loses visual cohesion.
The recommended approach is to use a unitless value, such as 1.5 or 1.6. A unitless value is a multiplier applied to the element's current font size — meaning if the font size changes anywhere in the inheritance chain, the line height adjusts automatically. If you instead wrote line-height: 24px on a paragraph and later increased the font size to 20px, the lines would overlap. Unitless values prevent that problem.
/* Recommended: unitless */
p {
font-size: 1rem;
line-height: 1.6; /* 1.6 × 16px = 25.6px of line height */
}
/* Acceptable: em-relative */
p {
font-size: 1rem;
line-height: 1.6em;
}
/* Risky: fixed pixels that won't adapt if font-size changes */
p {
font-size: 1rem;
line-height: 24px;
}
General guidance: body text reads most comfortably with a line-height between 1.4 and 1.8. Short UI labels or headings at large sizes can often use values closer to 1.1 or 1.2.
The text-align property controls how inline content (text and inline elements) is distributed horizontally within its block container. The four most common values are:
- left — text is aligned to the left edge (the default for left-to-right languages). Each line ends wherever the last word ends, creating a ragged right edge. This is the most readable alignment for long paragraphs.
- right — text is aligned to the right edge. Useful for table cells containing numbers, captions beside left-floated images, or RTL (right-to-left) language support.
- center — each line is centered within the container. Appropriate for short headings, pull quotes, or call-to-action text, but uncomfortable for multi-line body copy.
- justify — text is spaced so that both the left and right edges are flush. Produces a clean, book-like appearance, but can create irregular "rivers" of white space running through paragraphs, especially on narrow columns. Use with caution and consider pairing with the
hyphens: autoproperty to reduce awkward gaps.
h2 {
text-align: center;
}
p {
text-align: left; /* safest for long-form reading */
line-height: 1.6;
}
.price-cell {
text-align: right; /* numbers align cleanly on the decimal */
}
.magazine-column {
text-align: justify;
hyphens: auto; /* helps reduce awkward spacing */
}
Applying Colors with Named Values and Hex Codes
CSS supports several notations for specifying color. The simplest are named colors — plain English words that map to predefined color values. CSS defines 140+ named colors, from obvious ones like red, blue, and white to more descriptive ones like coral, teal, slategray, and lavender. Named colors are immediately readable in code and require no mental conversion, but they are limited to those 140+ options and rarely match a brand's precise color requirements.
h1 {
color: navy;
}
header {
background-color: whitesmoke;
}
.alert {
background-color: crimson;
color: white;
}
Hex codes are the most widely used color notation in professional web development. A hex code is a six-character string prefixed with #, written in hexadecimal (base 16, using digits 0–9 and letters A–F). The six characters form three pairs, each representing a color channel:
| Pair position | Channel | Range (hex) | Range (decimal) |
|---|---|---|---|
| Characters 1–2 | Red | 00 – FF | 0 – 255 |
| Characters 3–4 | Green | 00 – FF | 0 – 255 |
| Characters 5–6 | Blue | 00 – FF | 0 – 255 |
For example, #FF5733 has a red channel of FF (255 — maximum red), a green channel of 57 (87), and a blue channel of 33 (51), producing a vivid reddish-orange. Pure white is #FFFFFF (all channels at maximum), and pure black is #000000 (all channels at zero).
When both characters in each pair are identical, you can use the shorthand three-character form. The browser simply doubles each character internally:
#ffffff→#fff#000000→#000#aabbcc→#abc#ff3300→#f30
Hex codes are the standard output of virtually every design tool — Figma, Adobe XD, Photoshop, Sketch — making them the most practical notation for implementing a designer's color palette faithfully.
/* Full hex */
h2 {
color: #2C3E50; /* a dark blue-gray */
}
button.primary {
background-color: #3498DB; /* a medium blue */
color: #FFFFFF;
}
/* Shorthand hex */
footer {
background-color: #222; /* same as #222222 — very dark gray */
color: #fff; /* same as #ffffff — white */
}
Applying Colors with RGB Notation
The rgb() function expresses color using the same three-channel model as hex codes, but with decimal integers in the range 0–255 rather than hexadecimal pairs. Many developers find decimal values more intuitive because the numbers map directly to human-readable intensities:
rgb(255, 0, 0)— pure red (red channel maxed, others at zero)rgb(0, 255, 0)— pure greenrgb(0, 0, 255)— pure bluergb(255, 255, 255)— white (all channels maximum)rgb(0, 0, 0)— black (all channels zero)rgb(128, 128, 128)— medium gray (equal channels at mid-point)
Understanding RGB mixing helps you intuit color. Want a warm orange? You need high red, moderate green, and very little blue — something like rgb(255, 140, 0). Want a muted purple? Mix high red, low green, and high blue — rgb(150, 50, 180).
The rgba() function extends rgb() by adding a fourth argument: the alpha channel, which controls opacity. The alpha value is a decimal between 0 (fully transparent) and 1 (fully opaque). Fractional values like 0.5 produce 50% transparency. This is invaluable for effects such as semi-transparent overlays on hero images, frosted-glass UI panels, and subtle colored shadows.
/* Solid color with rgb() */
p {
color: rgb(44, 62, 80); /* dark blue-gray body text */
}
.card {
background-color: rgb(236, 240, 241); /* very light gray */
}
/* Semi-transparent overlay with rgba() */
.hero-overlay {
background-color: rgba(0, 0, 0, 0.5); /* black at 50% opacity */
}
.tooltip {
background-color: rgba(44, 62, 80, 0.9); /* almost opaque dark panel */
color: rgb(255, 255, 255);
}
.highlight {
background-color: rgba(255, 235, 59, 0.4); /* translucent yellow */
}
RGB and RGBA notation shine in scenarios where color values need to be generated or manipulated programmatically. A JavaScript function can compute a color at runtime — for instance, adjusting a chart bar's color based on a data value — and write it directly into a style attribute using the rgb() syntax, something that is far harder to do with hex strings.
Styling Text Color and Background Color
Two CSS properties work in direct partnership to establish the visual foundation of almost every element on a page: color and background-color.
The color property sets the foreground color — the color of rendered text and text decorations (underlines, strikethroughs) within the element. It is inherited, meaning child elements automatically receive the parent's text color unless they override it. This makes it efficient to set a base text color on the body and override only where needed.
The background-color property fills the background area of an element's box model — everything inside the border (including the padding area). By default, background color is transparent, which lets the parent's background show through. You can apply any valid color value: named, hex, rgb(), or rgba().
/* Global base colors */
body {
color: #333333; /* dark gray text — easier on the eyes than pure black */
background-color: #f9f9f9; /* off-white page background */
}
/* Component-level overrides */
header {
background-color: #1a1a2e; /* deep navy */
color: #e0e0e0; /* light gray text on dark background */
}
.card {
background-color: #ffffff;
color: #222222;
}
.badge-success {
background-color: #27ae60; /* green */
color: #ffffff;
}
.badge-warning {
background-color: #f39c12; /* amber */
color: #1a1a1a;
}
/* Using rgba for a glassy sidebar */
.sidebar {
background-color: rgba(255, 255, 255, 0.15);
color: #ffffff;
}
A critical consideration when pairing text and background colors is contrast. The Web Content Accessibility Guidelines (WCAG) define minimum contrast ratios to ensure that text is legible for users with low vision or color blindness. The standard recommendation is a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (roughly 18pt or 14pt bold). Tools such as the WebAIM Contrast Checker or built-in browser developer tools can measure the ratio between any two colors automatically.
As a practical rule: very dark text on a very light background, or very light text on a very dark background, almost always satisfies contrast requirements. Medium-value colors on similarly medium backgrounds — like light gray text on a white background — frequently fail. Always verify your choices rather than relying on intuition alone, because what looks fine on a bright monitor in a dark room may be completely unreadable on a phone in sunlight.
/* Good contrast — passes WCAG AA */
.readable {
color: #1a1a1a; /* near-black */
background-color: #ffffff; /* white */
}
/* Poor contrast — likely to fail WCAG checks */
.hard-to-read {
color: #aaaaaa; /* medium gray */
background-color: #ffffff; /* white — ratio roughly 2.3:1 — fails */
}
/* Good contrast on dark background */
.dark-theme {
color: #f0f0f0; /* near-white */
background-color: #121212; /* near-black */
}
By thoughtfully combining font-family, font-size, font-weight, line-height, text-align, color, and background-color, you have everything you need to create typography that is not only attractive but genuinely accessible and easy to read. These properties are the vocabulary of visual communication on the web, and understanding them in depth pays dividends in every project you build.