|$ curl https://forge-ai.dev/api/markdown?path=docs/css/typography
$cat docs/css-typography.md
updated Recently·25 min read·published

CSS Typography

CSSTypographyIntermediateIntermediate
Introduction

Typography is the foundation of web design. Studies show that 95% of web content is text, making typographic choices critical for readability, accessibility, and brand identity. CSS provides a rich set of properties for controlling type on the web, from basic font selection to advanced OpenType features.

A font stack (or font family stack) is a prioritized list of fonts. The browser tries each font in order until it finds one available on the system. Always end a font stack with a generic family keyword to ensure the browser has a fallback.

typography-basics.css
CSS
1body {
2 font-family: "Inter", "SF Pro", system-ui, -apple-system, sans-serif;
3 font-size: 1rem;
4 line-height: 1.6;
5 color: #E0E0E0;
6}
preview
font-family

The font-family property specifies a prioritized list of font family names and generic family names. The browser selects the first available font from the list. Generic families ensure the page is readable even when custom fonts fail to load.

Generic Families

Generic FamilyDescriptionExample (Mac)
serifLetters with small strokes (serifs) at the end of charactersTimes New Roman, Georgia, Merriweather
sans-serifLetters without serifs — clean and modernHelvetica, Arial, Inter, SF Pro
monospaceAll characters have the same widthSF Mono, Menlo, Consolas, Fira Code
cursiveHandwriting style, joined strokesBrush Script, Pacifico, Comic Sans
fantasyDecorative, ornamental charactersPapyrus, Impact, Joker

Font Stacks

font-stacks.css
CSS
1/* System font stack — fastest performance, matches OS */
2body {
3 font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
4}
5
6/* Modern sans-serif stack */
7.sans {
8 font-family: "Inter", "SF Pro", "Helvetica Neue", Arial, sans-serif;
9}
10
11/* Premium serif for body text */
12.serif {
13 font-family: "Merriweather", "Georgia", "Times New Roman", serif;
14}
15
16/* Code font stack */
17code {
18 font-family: "Fira Code", "SF Mono", Menlo, "Cascadia Code", monospace;
19}
20
21/* system-ui — uses the OS native UI font */
22.native { font-family: system-ui; }
23/* -apple-system — macOS/iOS San Francisco */
24.apple { font-family: -apple-system; }
preview
@font-face

The @font-face at-rule allows you to load custom fonts hosted on your server or via a CDN. Modern web font formats include WOFF2 (best compression), WOFF, and TTF.

Basic @font-face Declaration

font-face.css
CSS
1@font-face {
2 font-family: "Geist";
3 src: url("/fonts/geist.woff2") format("woff2"),
4 url("/fonts/geist.woff") format("woff");
5 font-weight: 400;
6 font-style: normal;
7 font-display: swap;
8 unicode-range: U+0000-00FF;
9}
10
11@font-face {
12 font-family: "Geist";
13 src: url("/fonts/geist-bold.woff2") format("woff2");
14 font-weight: 700;
15 font-style: normal;
16 font-display: swap;
17}

font-display Strategies

ValueBehaviorUse Case
autoBrowser default — usually a short block periodDefault, unknown performance
blockInvisible text for ~3s (block period), then swapFOIT — icon fonts, brand typography
swapFallback font shown immediately, swap when loadedBest UX — text visible immediately
fallbackShort block (~100ms), then swapBalance between FOIT and FOUT
optionalExtremely short block, may not swap if network is slowNon-critical typography

unicode-range

unicode-range limits a font-face to specific character ranges, enabling you to load smaller font files for Latin text while using a different font for CJK characters, emoji, or special symbols.

unicode-range.css
CSS
1/* Load a smaller font for basic Latin (A-Z, 0-9) */
2@font-face {
3 font-family: "MyFont";
4 src: url("myfont-latin.woff2") format("woff2");
5 unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC;
6}
7
8/* A different font for emoji */
9@font-face {
10 font-family: "MyFont";
11 src: url("myfont-emoji.woff2") format("woff2");
12 unicode-range: U+1F600-1F64F;
13}
🔥

pro tip

Always use font-display: swap for body text to avoid invisible text (FOIT). Reserve font-display: block for icon fonts or logotype where the custom font is essential to the design.
font-size

Font size can be specified using absolute units (px, pt, pc), relative units (em, rem, %), viewport units (vw, vh), or the CSS math functions clamp(), min(), and max().

Absolute vs Relative

font-size-units.css
CSS
1.absolute {
2 font-size: 16px; /* fixed size — ignores user preferences */
3}
4
5.relative-em {
6 font-size: 1.25em; /* relative to parent font-size */
7}
8
9.relative-rem {
10 font-size: 1.125rem; /* relative to root font-size (default 16px) */
11}
12
13.relative-percent {
14 font-size: 110%; /* 110% of parent */
15}
16
17.viewport {
18 font-size: 4vw; /* 4% of viewport width — scales with window */
19}

Fluid Typography with clamp()

The clamp(MIN, PREFERRED, MAX) function creates fluid type that scales smoothly between a minimum and maximum size. This eliminates the need for multiple breakpoints for font sizes.

fluid-typography.css
CSS
1/* Fluid heading — scales between 2rem and 4rem */
2h1 {
3 font-size: clamp(2rem, 1rem + 3vw, 4rem);
4}
5
6/* Fluid body text — subtle scaling */
7p {
8 font-size: clamp(0.875rem, 0.8rem + 0.5vw, 1.125rem);
9}
10
11/* Modular scale approach */
12:root {
13 --step-0: clamp(0.875rem, 0.8rem + 0.5vw, 1rem);
14 --step-1: clamp(1rem, 0.9rem + 0.8vw, 1.25rem);
15 --step-2: clamp(1.25rem, 1.1rem + 1.2vw, 1.75rem);
16 --step-3: clamp(1.75rem, 1.4rem + 2vw, 2.75rem);
17 --step-4: clamp(2.5rem, 1.8rem + 3vw, 4rem);
18}
preview
font-weight

Font weight controls the thickness of characters. Keywords and numeric values are interchangeable, with 400 typically mapping to normal and 700 to bold. Variable fonts enable continuous weight adjustment between 1 and 1000.

font-weight.css
CSS
1.thin { font-weight: 100; } /* Thin / Hairline */
2.light { font-weight: 300; } /* Light */
3.normal { font-weight: 400; } /* Normal / Regular */
4.medium { font-weight: 500; } /* Medium */
5.semibold { font-weight: 600; } /* Semi Bold */
6.bold { font-weight: 700; } /* Bold */
7.extrabold { font-weight: 800; } /* Extra Bold */
8.black { font-weight: 900; } /* Black / Heavy */
9
10/* Keyword equivalents */
11.normal { font-weight: normal; } /* = 400 */
12.bold { font-weight: bold; } /* = 700 */
13
14/* Variable fonts — any weight between 1 and 1000 */
15.variable {
16 font-family: "Inter", sans-serif;
17 font-weight: 450; /* custom weight between Regular and Medium */
18 font-variation-settings: "wght" 450;
19}
preview
font-style & font-variant

font-style controls italicization and obliqueness. font-variant enables OpenType features like small-caps, ligatures, and numeric variants.

font-style-variant.css
CSS
1/* font-style */
2.italic { font-style: italic; } /* uses the italic variant of the font */
3.oblique { font-style: oblique; } /* artificially slanted */
4.oblique { font-style: oblique 10deg; } /* oblique with angle */
5.normal { font-style: normal; }
6
7/* font-variant */
8.small-caps { font-variant: small-caps; }
9
10/* font-variant sub-properties */
11.contextual-ligatures {
12 font-variant-ligatures: common-ligatures contextual;
13}
14.tabular-nums {
15 font-variant-numeric: tabular-nums;
16}
17.proportional {
18 font-variant-numeric: proportional-nums;
19}
20.east-asian {
21 font-variant-east-asian: jis78;
22}
23
24/* Shorthand */
25.all-small { font-variant: all-small-caps; }
26}
preview
line-height

Line height controls the vertical space between lines of text. Unitless values are recommended because they are relative to the font-size and work correctly with inheritance. A line height of 1.5 to 1.7 is considered optimal for body text readability.

line-height.css
CSS
1/* Unitless (recommended) — relative to font-size */
2.tight { line-height: 1.2; } /* headings */
3.normal { line-height: 1.5; } /* default */
4.roomy { line-height: 1.7; } /* body text */
5.spacious { line-height: 2; } /* extended readability */
6
7/* Length values — not recommended */
8.pixels { line-height: 24px; } /* fixed — does not scale with font-size */
9.em-unit { line-height: 1.5em; } /* em-based — can produce unexpected results */
10
11/* Normal — browser default (~1.2) */
12.default { line-height: normal; }
13
14/* Leading tip for headings */
15h1, h2, h3 {
16 line-height: 1.15; /* tighter for larger text */
17}
18
19p {
20 line-height: 1.65; /* looser for body text readability */
21}
preview
letter-spacing & word-spacing

letter-spacing (tracking) controls the horizontal space between characters. word-spacing controls the space between words. Small adjustments can significantly impact readability and tone.

letter-word-spacing.css
CSS
1/* letter-spacing (tracking) */
2.condensed { letter-spacing: -0.02em; } /* tighter — use sparingly */
3.normal { letter-spacing: normal; } /* default (0) */
4.expanded { letter-spacing: 0.05em; } /* more open */
5.heading { letter-spacing: -0.03em; } /* popular for modern headings */
6.uppercase { letter-spacing: 0.08em; } /* common for all-caps text */
7
8/* word-spacing */
9.normal { word-spacing: normal; } /* default (0) */
10.expanded-words { word-spacing: 0.25em; } /* extra space between words */
11
12/* Combined */
13.meta {
14 text-transform: uppercase;
15 font-size: 0.75rem;
16 letter-spacing: 0.1em; /* improves uppercase readability */
17 word-spacing: 0.15em;
18}
preview
text-align & text-indent

text-align controls horizontal alignment of inline content within its block container. text-indent specifies the indentation of the first line of a block of text.

text-align-indent.css
CSS
1/* text-align */
2.left { text-align: left; } /* default for LTR languages */
3.right { text-align: right; }
4.center { text-align: center; }
5.justify { text-align: justify; } /* stretches lines to fill width */
6
7/* text-indent */
8.indent {
9 text-indent: 2em; /* first line indented by 2 character widths */
10}
11
12.hanging {
13 text-indent: -2em; /* hanging indent — first line extends left */
14 padding-left: 2em; /* needed with negative text-indent */
15}
16
17/* Combine with text-align */
18article p {
19 text-align: justify;
20 text-indent: 1.5em;
21}
22
23/* No indent on first paragraph after a heading */
24h2 + p, h3 + p {
25 text-indent: 0;
26}
preview
text-decoration

The text-decoration shorthand and its sub-properties give fine control over decorative lines on text. Modern CSS supports underline offset, thickness, and skip-ink behavior.

text-decoration.css
CSS
1/* text-decoration shorthand */
2.underline {
3 text-decoration: underline;
4}
5
6.overline {
7 text-decoration: overline;
8}
9
10.line-through {
11 text-decoration: line-through;
12}
13
14.none {
15 text-decoration: none; /* remove decoration (common on links) */
16}
17
18/* Sub-properties for fine control */
19.fancy-underline {
20 text-decoration-line: underline;
21 text-decoration-style: wavy; /* solid | double | dotted | dashed | wavy */
22 text-decoration-color: #00FF41; /* custom color */
23 text-decoration-thickness: 2px; /* line thickness */
24 text-underline-offset: 4px; /* space between text and line */
25 text-decoration-skip-ink: auto; /* skips descenders for cleaner look */
26}
27
28/* Multiple lines */
29.both {
30 text-decoration: underline overline line-through;
31 text-decoration-thickness: 1px;
32 text-decoration-color: #FFB000;
33}
preview
text-transform

text-transform changes the capitalization of text without altering the underlying content. This is a presentational change only — screen readers read the original text.

text-transform.css
CSS
1.uppercase {
2 text-transform: uppercase;
3 letter-spacing: 0.08em; /* improves readability in all-caps */
4}
5
6.lowercase {
7 text-transform: lowercase;
8}
9
10.capitalize {
11 text-transform: capitalize; /* capitalizes first letter of each word */
12}
13
14.full-width {
15 text-transform: full-width; /* forces characters into full-width form */
16}
17
18.full-size-kana {
19 text-transform: full-size-kana; /* converts small kana to full-size */
20}
21
22.none {
23 text-transform: none; /* preserves original casing */
24}
preview
text-shadow

text-shadow adds drop shadows to text. The syntax is: offset-x offset-y blur-radius color. Multiple shadows can be layered by comma-separating them.

text-shadow.css
CSS
1/* Simple drop shadow */
2.simple {
3 text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
4}
5
6/* Hard shadow (no blur) */
7.hard {
8 text-shadow: 4px 4px 0 #00FF41;
9}
10
11/* Glow effect */
12.glow {
13 text-shadow: 0 0 10px #00FF41, 0 0 20px #00FF41;
14}
15
16/* Layered/depth shadow */
17.layered {
18 text-shadow:
19 1px 1px 0 #222,
20 2px 2px 0 #333,
21 3px 3px 0 #444;
22}
23
24/* Neumorphism-style */
25.emboss {
26 text-shadow:
27 1px 1px 1px rgba(0, 0, 0, 0.8),
28 -1px -1px 1px rgba(255, 255, 255, 0.1);
29}
30
31/* Neon text */
32.neon {
33 color: #00FF41;
34 text-shadow:
35 0 0 5px rgba(0, 255, 65, 0.5),
36 0 0 10px rgba(0, 255, 65, 0.3),
37 0 0 20px rgba(0, 255, 65, 0.2);
38}
preview
White Space & Text Wrapping

Controlling how text wraps and how white space is handled is critical for responsive design, code display, and preventing layout breaks from long unbroken strings.

white-space

white-space.css
CSS
1.normal { white-space: normal; } /* collapse whitespace, wrap */
2.nowrap { white-space: nowrap; } /* collapse, no wrap */
3.pre { white-space: pre; } /* preserve, no wrap (like <pre>) */
4.pre-wrap { white-space: pre-wrap; } /* preserve, wraps */
5.pre-line { white-space: pre-line; } /* collapse, preserve newlines */
6.break-spaces { white-space: break-spaces; } /* like pre-wrap, preserves spaces */

Word Break & Overflow Wrap

word-break.css
CSS
1.break-word {
2 overflow-wrap: break-word; /* break long words if needed */
3}
4
5.break-all {
6 word-break: break-all; /* break at any character */
7}
8
9.keep-all {
10 word-break: keep-all; /* don't break CJK words */
11}
12
13.hyphenate {
14 hyphens: auto; /* auto-hyphenation */
15}
16
17.ellipsis {
18 white-space: nowrap;
19 overflow: hidden;
20 text-overflow: ellipsis; /* show ... for overflow */
21}
22
23/* Multi-line ellipsis (line-clamp) */
24.clamp-3 {
25 display: -webkit-box;
26 -webkit-line-clamp: 3;
27 -webkit-box-orient: vertical;
28 overflow: hidden;
29}
preview
List Styling

CSS provides properties for styling ordered and unordered lists, including marker types, positioning, and custom markers using counters and images.

list-style.css
CSS
1/* list-style-type */
2ul { list-style-type: disc; } /* default */
3ul { list-style-type: circle; }
4ul { list-style-type: square; }
5ul { list-style-type: none; } /* remove markers */
6
7ol { list-style-type: decimal; } /* 1, 2, 3 (default) */
8ol { list-style-type: lower-roman; } /* i, ii, iii */
9ol { list-style-type: upper-roman; } /* I, II, III */
10ol { list-style-type: lower-alpha; } /* a, b, c */
11ol { list-style-type: georgian; } /* Georgian numbering */
12
13/* list-style-position */
14ol { list-style-position: inside; } /* marker inside the list item box */
15ol { list-style-position: outside; } /* marker outside (default) */
16
17/* list-style-image */
18ul { list-style-image: url("arrow.svg"); }
19
20/* Custom counters */
21.custom-counter {
22 list-style: none;
23 counter-reset: my-counter;
24}
25
26.custom-counter li {
27 counter-increment: my-counter;
28}
29
30.custom-counter li::before {
31 content: counter(my-counter, upper-alpha) ". ";
32 color: #00FF41;
33 font-weight: bold;
34}
preview
Web Font Performance

Web fonts impact page load performance through file size and rendering delay. Understanding FOIT (Flash of Invisible Text) vs FOUT (Flash of Unstyled Text) is essential for choosing the right loading strategy.

FOIT vs FOUT

BehaviorDescriptionBest Avoided By
FOITFlash of Invisible Text — text hidden while font loadsfont-display: swap
FOUTFlash of Unstyled Text — fallback font shown, then swappedSize-match fallback to reduce layout shift

Performance Strategies

font-performance.css
CSS
1/* 1. Preload critical fonts in HTML */
2/* <link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin> */
3
4/* 2. font-display: swap for body text */
5@font-face {
6 font-family: "Inter";
7 src: url("/fonts/inter.woff2") format("woff2");
8 font-display: swap;
9}
10
11/* 3. Subsetting — only load characters you need */
12/* Use unicode-range to split fonts */
13
14/* 4. Self-host fonts instead of using CDNs */
15/* Eliminates DNS lookup, reduces connection overhead */
16
17/* 5. Use WOFF2 format — best compression (30-50% smaller than WOFF) */
18@font-face {
19 font-family: "MyFont";
20 src: url("/fonts/myfont.woff2") format("woff2");
21}
22
23/* 6. Fallback font sizing — minimize CLS */
24body {
25 font-family: "Inter", system-ui, sans-serif;
26}
27
28/* Size-match fallback: adjust fallback to match custom font metrics */
29@font-face {
30 font-family: "Inter-fallback";
31 src: local(system-ui);
32 size-adjust: 107%;
33 ascent-override: 90%;
34 descent-override: 22%;
35}

best practice

Use font-display: optionalfor non-critical fonts (decorative, icon fonts). This tells the browser to use the fallback font if the custom font hasn't loaded in a very short window (~50ms), preventing FOUT on slow connections.
Best Practices
Use rem for font sizes — respects user browser font-size preferences
Use unitless line-height — scales correctly with font-size and inheritance
Limit font families to 2-3 per page for performance and visual consistency
Self-host custom fonts to eliminate DNS lookups and external requests
Use font-display: swap for body text to prevent invisible text (FOIT)
Prefer clamp() for fluid typography over multiple breakpoints
Set letter-spacing on uppercase text to improve readability
Keep line length between 45-75 characters per line for optimal readability
Use variable fonts to reduce HTTP requests (multiple weights in one file)
Subset fonts to include only needed character ranges (especially Latin)
Test typography on real devices — not just in dev tools
🔥

pro tip

Consider using a modular scale for your type system. Tools like Type Scale (typescale.com) help generate harmonious size progressions. Pair this with a consistent vertical rhythm achieved through line-height and margin multipliers.
$Blueprint — Engineering Documentation·Section ID: CSS-03·Revision: 1.0