✍️ From Plain to Professional
In Module 5 you learned the mechanics of CSS — selectors, specificity, the box model, units, and colors. Now it is time to put them to work. Typography and visual design are what separate a page that works from a page that looks professional.
Typography alone accounts for roughly 95% of web design — most of what users see is text. The remaining 5% is the visual polish: backgrounds, gradients, borders, and shadows that create depth and hierarchy. Master these, and your pages will look as good as any modern website.
🎯 By the end of this module, you will:
- Build robust font stacks with system fonts and web fonts
- Load Google Fonts and self-hosted fonts with
@font-face - Control line height, letter spacing, word spacing, and text alignment
- Style text with decoration, transform, shadow, and overflow
- Set background colors, images, and control their size and position
- Create linear and radial gradients for modern UI effects
- Use border-radius, box-shadow, and text-shadow for visual depth
🔤 Font Families & Web Fonts
The font-family property controls which typeface the browser uses. You provide a font stack — an ordered list of fonts. The browser tries the first font; if it is not available, it moves to the next, and so on until it finds one that works.
Font Stacks
Always end your stack with a generic family — a keyword that tells the browser to pick any available font of that category. The five generic families are: serif, sans-serif, monospace, cursive, and fantasy.
Generic Font Families
/* Font stack: specific → generic fallback */ body { font-family: "Inter", "Segoe UI", system-ui, sans-serif; } h1, h2, h3 { font-family: "Playfair Display", Georgia, serif; } code, pre { font-family: "JetBrains Mono", "Fira Code", monospace; }
System Font Stack
The system-ui keyword tells the browser to use the operating system's default UI font. On macOS that is San Francisco, on Windows it is Segoe UI, on Android it is Roboto. Zero download, instant rendering, and native look.
/* Modern system font stack — no downloads needed */ body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
Loading Web Fonts (Google Fonts)
Google Fonts hosts 1,500+ free fonts. Add a <link> element in your HTML <head> and the font becomes available in your CSS. Only load the weights you actually use — each weight adds to page load time.
<!-- In <head> — Load Inter (weights 400, 600, 700) --> <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">
display=swap do?
It tells the browser to show text immediately in a fallback font while the web font loads. Without it, text may be invisible for seconds on slow connections (FOIT — Flash of Invisible Text). With swap, users see a brief font change instead (FOUT — Flash of Unstyled Text), which is much better.
Self-hosting with @font-face
Instead of relying on Google's servers, you can host font files yourself. Download the .woff2 file (the most efficient format) and declare it with @font-face. This gives you full control, better privacy, and works offline.
/* Self-host a font */ @font-face { font-family: "MyCustomFont"; src: url("fonts/my-font.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
woff2 format — it is 30% smaller than woff and supported by all modern browsers.
Font Size & Weight
font-size controls how large text appears. Use rem for predictable, accessible sizing (recall Module 5). font-weight controls boldness — values range from 100 (thin) to 900 (black), with 400 being normal and 700 being bold.
h1 { font-size: 2.5rem; /* 40px at default */ font-weight: 700; /* Bold */ } h2 { font-size: 1.75rem; /* 28px */ font-weight: 600; /* Semi-bold */ } body { font-size: 1rem; /* 16px — the browser default */ font-weight: 400; /* Normal */ } .caption { font-size: 0.875rem; /* 14px — small text */ font-weight: 300; /* Light */ }
📝 Text Styling
Beyond font family and size, CSS gives you fine-grained control over how text appears: spacing between lines and letters, alignment, decoration, transformation, and overflow handling.
Line Height
line-height controls the space between lines of text. A unitless value like 1.6 means 1.6 times the font size. This is the most important readability setting — too tight and text feels cramped, too loose and the eye loses track.
body { line-height: 1.6; /* Good for body text */ } h1, h2, h3 { line-height: 1.2; /* Tighter for headings */ }
line-height as a unitless number (e.g., 1.6), not a fixed value like 24px. A unitless value scales proportionally when the font size changes. A fixed value does not — and creates overlap if the text is ever resized.
Letter & Word Spacing
letter-spacing adjusts the space between characters. word-spacing adjusts the space between words. Small values make a big visual difference — use them sparingly.
.uppercase-heading { text-transform: uppercase; letter-spacing: 0.1em; /* Spread uppercase letters */ font-size: 0.875rem; font-weight: 600; } .tight-heading { letter-spacing: -0.02em; /* Slightly tighter — common for large headings */ font-size: 3rem; }
Text Alignment
text-align controls horizontal alignment within a block element. The four values: left (default for LTR languages), right, center, and justify (stretches lines to fill the full width — use with caution, as it can create uneven spacing).
Text Decoration
text-decoration adds or removes lines on text. Links have underline by default. You can customize the line style, color, and thickness.
/* Remove default link underline, add on hover */ a { text-decoration: none; color: #3b82f6; } a:hover { text-decoration: underline; text-underline-offset: 4px; } /* Strikethrough for deleted prices */ .original-price { text-decoration: line-through red; color: #999; }
Text Transform
text-transform changes the capitalization of text without editing the HTML. This keeps content accessible (screen readers see the original) while changing the visual appearance.
text-transform Values
uppercase — ALL LETTERS BECOME CAPITALS. Common for labels, badges, and small headings.
lowercase — all letters become lowercase.
capitalize — First Letter Of Each Word Is Capitalized.
none — Removes any inherited transformation.
Text Overflow & Truncation
When text overflows its container, you can clip it with an ellipsis (...). This requires three properties working together:
/* Single-line truncation with ellipsis */ .truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 300px; } /* Multi-line truncation (clamp to 3 lines) */ .line-clamp { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
🖼️ Backgrounds
Every element has a background layer behind its content and padding. By default it is transparent, but you can fill it with a solid color, an image, or (as you will see in the next section) a gradient.
Background Color
The simplest background. It fills the entire content + padding area (but not the margin). You already know how to define colors from Module 5.
.card { background-color: #f8fafc; padding: 24px; } .highlight { background-color: rgba(59, 130, 246, 0.1); /* Semi-transparent blue */ padding: 4px 8px; border-radius: 4px; }
Background Image
background-image places an image behind the element's content. By default, the image tiles (repeats) to fill the space. You control this with related properties.
| Property | Purpose | Common Values |
|---|---|---|
background-image |
Sets the image | url("photo.jpg") |
background-size |
How the image scales | cover, contain, 100% auto |
background-position |
Where the image sits | center, top right, 50% 50% |
background-repeat |
Whether the image tiles | no-repeat, repeat, repeat-x |
background-attachment |
Scrolling behavior | scroll, fixed |
.hero { background-image: url("hero-bg.jpg"); background-size: cover; /* Scale to fill, crop excess */ background-position: center; /* Keep the focal point centered */ background-repeat: no-repeat; /* Don't tile */ min-height: 400px; }
cover vs contain:
cover scales the image to fill the element completely, cropping any excess. contain scales the image to fit entirely inside the element, leaving empty space if the aspect ratios differ. Use cover for hero sections and full-bleed backgrounds; use contain for logos and diagrams where nothing should be cut off.
The background Shorthand
You can combine all background properties into one shorthand declaration. The order is flexible, but the common convention is: color image position/size repeat.
/* Shorthand — all in one line */ .hero { background: #1a1a2e url("hero.jpg") center/cover no-repeat; }
🌈 Gradients
Gradients are CSS-generated images — smooth transitions between two or more colors. They are set with background-image (or the background shorthand) and require zero image files.
Linear Gradients
A linear-gradient() transitions colors along a straight line. You specify a direction (or angle) and two or more color stops.
/* Top to bottom (default) */ .gradient-1 { background: linear-gradient(#3b82f6, #8b5cf6); padding: 40px; color: white; border-radius: 12px; margin-bottom: 16px; } /* Left to right */ .gradient-2 { background: linear-gradient(to right, #f59e0b, #ef4444); padding: 40px; color: white; border-radius: 12px; margin-bottom: 16px; } /* Diagonal with angle */ .gradient-3 { background: linear-gradient(135deg, #06b6d4, #3b82f6, #8b5cf6); padding: 40px; color: white; border-radius: 12px; }
Gradient Direction Keywords
to bottom — Top to bottom (default). Same as 180deg.
to right — Left to right. Same as 90deg.
to top right — Diagonal to the top-right corner.
135deg — Any angle. 0deg is up, 90deg is right, 180deg is down.
Radial Gradients
A radial-gradient() radiates colors outward from a center point, creating circular or elliptical effects.
.spotlight { background: radial-gradient(circle at center, #3b82f6, #1e1b4b); padding: 60px; color: white; text-align: center; border-radius: 12px; }
Gradient + Image Overlay
A powerful technique: layer a semi-transparent gradient over a background image to ensure text remains readable. CSS allows multiple backgrounds stacked with commas — the first one is on top.
/* Dark overlay on top of a photo */ .hero { background: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url("hero.jpg") center/cover no-repeat; color: white; }
🔲 Borders & Rounded Corners
Borders are the visible lines between an element's padding and margin. border-radius rounds the corners — from subtle rounding to full circles.
Border Syntax
The border shorthand takes three values: width, style, color. You can also target individual sides with border-top, border-right, border-bottom, border-left.
/* Shorthand: width style color */ .card { border: 1px solid #e2e8f0; padding: 24px; margin-bottom: 16px; } /* Bottom-only accent border */ .section-heading { border-bottom: 3px solid #3b82f6; padding-bottom: 8px; } /* Dashed and dotted styles */ .note { border: 2px dashed #f59e0b; padding: 16px; }
Border Styles
solid — A continuous line. The most common style.
dashed — A series of short dashes.
dotted — A series of dots.
double — Two parallel lines. The width must be at least 3px to see both lines.
none — No border. Useful for removing default borders from buttons and inputs.
border-radius: Rounded Corners
border-radius rounds the corners of an element. One value rounds all four corners equally. You can also set each corner individually.
/* Subtle rounding — modern UI standard */ .card { border-radius: 8px; background: #f1f5f9; padding: 24px; margin-bottom: 16px; } /* Pill-shaped button */ .pill { border-radius: 9999px; background: #3b82f6; color: white; padding: 8px 24px; display: inline-block; margin-bottom: 16px; } /* Perfect circle (equal width and height) */ .avatar { width: 80px; height: 80px; border-radius: 50%; background: #8b5cf6; color: white; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 1.5rem; }
border-radius: 50% creates an ellipse, not a circle. For perfect circles, ensure both width and height are the same. For pill shapes on elements of varying width, use a very large value like 9999px.
outline vs border
outline looks like a border but does not take up space in the box model — it does not affect layout. It is drawn outside the border. The browser uses it for focus indicators on interactive elements. Never remove it without providing an alternative focus style.
/* Custom focus style — never just "outline: none" */ button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; }
🔮 Shadows
Shadows create depth and visual hierarchy. CSS has two shadow properties: box-shadow for elements and text-shadow for text.
box-shadow
box-shadow adds one or more shadows around an element's box. The syntax: offset-x offset-y blur spread color. Blur and spread are optional.
/* Subtle elevation — great for cards */ .card-sm { box-shadow: 0 1px 3px rgba(0,0,0,0.12); padding: 24px; border-radius: 8px; background: white; margin-bottom: 24px; } /* Medium elevation */ .card-md { box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1); padding: 24px; border-radius: 8px; background: white; margin-bottom: 24px; } /* Large elevation — modals, popups */ .card-lg { box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1); padding: 24px; border-radius: 8px; background: white; }
box-shadow Values
rgba() with low opacity for natural-looking shadows.
inset keyword before the values to create an inner shadow: box-shadow: inset 0 2px 4px rgba(0,0,0,0.1). This creates a pressed or recessed effect — useful for input fields and toggle buttons.
text-shadow
text-shadow adds a shadow behind text. The syntax is simpler: offset-x offset-y blur color (no spread value). Use subtle shadows for readability on images, or decorative shadows for headings.
/* Subtle readability shadow */ .hero-title { color: white; text-shadow: 0 2px 4px rgba(0,0,0,0.5); font-size: 3rem; } /* Decorative glow effect */ .neon { color: #06b6d4; text-shadow: 0 0 10px #06b6d4, 0 0 40px #06b6d4; font-size: 2rem; }
✏️ Practice: Typography & Visual Design
Test your understanding of fonts, text styling, backgrounds, gradients, and shadows.
Exercise 1: Build a Font Stack
Write a CSS rule for the body element that: (1) uses Inter as the primary font, (2) falls back to the system UI font, (3) ends with the correct generic family.
Then write a second rule for headings that uses Playfair Display with a serif fallback.
Reveal Solution
body { font-family: "Inter", system-ui, sans-serif; } — Inter is the primary, system-ui is the native OS font, and sans-serif is the generic fallback.
h1, h2, h3 { font-family: "Playfair Display", Georgia, serif; } — Playfair Display is a serif web font, Georgia is a well-known system serif, and serif is the generic family.
Exercise 2: Typography Readability
This paragraph is hard to read. Identify all the problems and write the CSS to fix them:
p { font-size: 11px; line-height: 12px; letter-spacing: 0.2em; text-align: justify; font-family: fantasy; }
Reveal Solution
❌ Font size too small — 11px is below the recommended minimum of 16px for body text. Fix: font-size: 1rem;
❌ Line height too tight and uses fixed units — 12px line-height on 11px text is barely any spacing. Fix: line-height: 1.6; (unitless, scales with font size).
❌ Letter spacing too wide — 0.2em is extreme for body text. Fix: remove letter-spacing or use normal. Only use positive letter-spacing on uppercase labels (around 0.05–0.1em).
❌ text-align: justify creates rivers — uneven word spacing reduces readability. Fix: text-align: left;
❌ fantasy is unpredictable — every browser renders it differently. Fix: use a proper font stack like "Inter", system-ui, sans-serif.
Exercise 3: Create a Card with Visual Polish
Write CSS for a .card class that has: a white background, 24px padding, 8px rounded corners, a subtle box shadow, and a 1px solid light-gray border. Then add a :hover state that increases the shadow.
Reveal Solution
.card { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.12); border: 1px solid #e2e8f0; }
.card:hover { box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1); }
💡 The hover shadow is larger (more offset-y, more blur) to create the illusion that the card lifts off the page. This is a common interaction pattern in modern UI design.
Exercise 4: Gradient Hero Section
Write the CSS for a .hero section with a diagonal gradient from blue (#3b82f6) to purple (#8b5cf6), white centered text, and a minimum height of 400px.
Reveal Solution
.hero { background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: white; text-align: center; min-height: 400px; display: flex; align-items: center; justify-content: center; }
💡 135deg creates a top-left to bottom-right diagonal. We use display: flex with centering to place the text in the middle of the hero. You will learn Flexbox in detail in Module 7.
📋 Module Summary
Build font stacks ending with a generic family. Use system-ui for zero-download native fonts. Load web fonts with <link> or @font-face. Limit to 2 families and 3 weights max.
Use unitless line-height (1.5–1.6 for body). letter-spacing for uppercase labels, negative values for large headings. text-overflow: ellipsis for truncation.
background-size: cover for hero images. contain for logos. Combine with background-position: center and no-repeat.
linear-gradient() for straight transitions, radial-gradient() for circular effects. Layer gradients over images for text readability.
border: width style color. border-radius for rounded corners — 50% for circles, 9999px for pills. Never remove outline without a replacement.
box-shadow for element depth (cards, modals). text-shadow for text readability over images. Use low-opacity rgba() for natural-looking shadows.