CSS Typography and Text Styling
1. CSS Font Properties (font-family, font-size, font-weight)
| Property | Values | Description | Default |
|---|---|---|---|
| font-family | family-name | generic-family |
Font family stack (fallback chain) | Browser default |
| font-size | length | % |
Font size (absolute or relative) | medium (16px) |
| font-weight | 100-900 | normal | bold | lighter | bolder |
Font weight (thickness) | 400 (normal) |
| font-style | normal | italic | oblique [angle] |
Font style | normal |
| font-variant | normal | small-caps | all-small-caps |
Font variant (caps, ligatures, numerals) | normal |
| font-stretch | 50%-200% | ultra-condensed to ultra-expanded |
Font width (condensed/expanded) | 100% (normal) |
| font | style variant weight size/line-height family |
Shorthand for all font properties | - |
| font-synthesis | none | weight | style | small-caps |
Control browser font synthesis | weight style |
| font-size-adjust | number | none |
Preserve x-height across fallback fonts | none |
| font-kerning | auto | normal | none |
Control kerning (letter spacing pairs) | auto |
| font-optical-sizing | auto | none |
Optimize font rendering at different sizes | auto |
| @font-face Descriptor | Values | Description | Required |
|---|---|---|---|
| font-family | name |
Name to reference font | ✓ Yes |
| src | url() format() |
Font file URL and format | ✓ Yes |
| font-weight | 100-900 | range |
Weight(s) this file supports | Optional |
| font-style | normal | italic | oblique |
Style this file supports | Optional |
| font-display | auto | block | swap | fallback | optional |
Loading behavior strategy | Optional |
| unicode-range | U+0-10FFFF |
Character range this font covers | Optional |
| font-stretch | 50%-200% |
Width range this file supports | Optional |
Example: Font family and @font-face
/* Font stack with fallbacks */
body {
font-family:
"Inter",
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
sans-serif;
}
/* Load custom web font */
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900; /* Variable font weight range */
font-style: normal;
font-display: swap; /* Show fallback immediately */
}
/* Load specific font weight */
@font-face {
font-family: "Roboto";
src: url("/fonts/roboto-regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "Roboto";
src: url("/fonts/roboto-bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
}
/* Unicode range for subsetting */
@font-face {
font-family: "Icons";
src: url("/fonts/icons.woff2") format("woff2");
unicode-range: U+E000-U+F8FF; /* Private Use Area */
}
Example: Font properties usage
/* Font shorthand */
h1 {
/* font: style variant weight size/line-height family */
font: italic small-caps 700 2rem/1.2 Georgia, serif;
}
/* Individual properties */
p {
font-family: "Inter", sans-serif;
font-size: 1rem;
font-weight: 400;
font-style: normal;
}
/* Variable font weights */
.light { font-weight: 300; }
.regular { font-weight: 400; }
.medium { font-weight: 500; }
.semibold { font-weight: 600; }
.bold { font-weight: 700; }
/* Font size adjustment for consistent x-height */
body {
font-family: "Primary", "Fallback", sans-serif;
font-size-adjust: 0.5; /* Preserves x-height across fonts */
}
/* Disable synthetic bold/italic */
.no-synthesis {
font-synthesis: none; /* Don't fake bold/italic if not available */
}
2. Font Display Strategies (font-display) and Performance
| font-display | Block Period | Swap Period | Behavior | Use Case |
|---|---|---|---|---|
| auto | Browser default | Browser default | Browser decides strategy | Default (not recommended) |
| block | ~3 seconds | Infinite | Block, then swap when loaded | Icons, critical branding |
| swap | ~0ms | Infinite | Show fallback immediately, swap when loaded | Body text, most common |
| fallback | ~100ms | ~3 seconds | Brief block, short swap period | Balance performance/appearance |
| optional | ~100ms | None | Use if cached, otherwise fallback | Non-critical, fast-loading priority |
| Loading Issue | Problem | Solution | Implementation |
|---|---|---|---|
| FOIT | Flash of Invisible Text | Use font-display: swap | @font-face { font-display: swap; } |
| FOUT | Flash of Unstyled Text | Match fallback font metrics | font-size-adjust: 0.5; |
| Layout Shift | Content jumps when font loads | Size-adjust descriptor, preload | size-adjust: 95%; |
| Slow Loading | Fonts take long to download | Preload critical fonts | <link rel="preload"> |
| Multiple Requests | Too many font files | Use variable fonts | Single file with weight range |
Example: Font display strategies
/* Recommended: swap for body text */
@font-face {
font-family: "Body";
src: url("/fonts/body.woff2") format("woff2");
font-display: swap; /* Show fallback immediately */
}
/* Block for icons (prevent invisible icons) */
@font-face {
font-family: "Icons";
src: url("/fonts/icons.woff2") format("woff2");
font-display: block; /* Wait for icons to load */
}
/* Optional for non-critical decorative fonts */
@font-face {
font-family: "Decorative";
src: url("/fonts/decorative.woff2") format("woff2");
font-display: optional; /* Only use if already cached */
}
/* Fallback for balance */
@font-face {
font-family: "Headings";
src: url("/fonts/headings.woff2") format("woff2");
font-display: fallback; /* Brief wait, short swap */
}
Example: Optimize font loading
<!-- Preload critical fonts -->
<link rel="preload"
href="/fonts/inter-var.woff2"
as="font"
type="font/woff2"
crossorigin>
<!-- Font display CSS -->
<style>
/* Match fallback font metrics to reduce layout shift */
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900;
font-display: swap;
/* Adjust fallback font size */
size-adjust: 100%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: "Inter",
-apple-system,
BlinkMacSystemFont,
sans-serif;
/* Preserve x-height when fallback is shown */
font-size-adjust: 0.5;
}
/* Loading class for progressive enhancement */
.fonts-loading body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
</style>
Note:
font-display: swap is recommended for most
cases to prevent FOIT. Preload critical fonts with <link rel="preload">. Use
size-adjust descriptor to minimize layout shift.
3. Variable Fonts and Font Variations
| Property | Values | Description | Advantage |
|---|---|---|---|
| font-variation-settings | "axis" value |
Low-level control of variable font axes | Access custom axes |
| font-weight | 100-900 |
Weight axis (high-level) | Any value in range |
| font-stretch | 50%-200% |
Width axis (high-level) | Smooth transitions |
| font-style | oblique [angle] |
Slant axis (high-level) | Custom italic angles |
| font-optical-sizing | auto | none |
Optical size axis (opsz) | Size-specific optimization |
| Standard Axis | Code | Range | Description |
|---|---|---|---|
| Weight | wght |
1-1000 | Thickness of strokes |
| Width | wdth |
50-200 | Condensed to expanded |
| Slant | slnt |
-90 to 90 | Angle of slant (oblique) |
| Optical Size | opsz |
6-72+ | Optimize for font size |
| Italic | ital |
0-1 | Roman to italic (binary) |
Example: Variable font usage
/* Load variable font */
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900; /* Supports full weight range */
font-stretch: 50% 200%; /* Supports width range */
font-style: oblique 0deg 10deg; /* Supports slant range */
}
/* Use high-level properties (preferred) */
h1 {
font-family: "Inter", sans-serif;
font-weight: 350; /* Any value 100-900 */
font-stretch: 115%; /* Slightly expanded */
}
h2 {
font-weight: 650; /* Custom weight between 600-700 */
}
/* Use font-variation-settings for custom axes */
.custom {
font-variation-settings:
"wght" 450, /* Weight */
"wdth" 100, /* Width */
"slnt" -5, /* Slant */
"GRAD" 50; /* Custom axis (Grade) */
}
/* Animate weight on hover */
.button {
font-weight: 400;
transition: font-weight 0.3s;
}
.button:hover {
font-weight: 600; /* Smooth weight transition */
}
Example: Advanced variable font techniques
/* Responsive typography with variable fonts */
h1 {
font-family: "Recursive", sans-serif;
/* Lighter weight at larger sizes */
font-weight: clamp(300, 900 - 20vw, 700);
/* More condensed at smaller viewports */
font-stretch: clamp(75%, 50% + 10vw, 125%);
}
/* Optical sizing (automatic) */
.auto-optical {
font-optical-sizing: auto; /* Adjusts opsz based on font-size */
}
/* Manual optical sizing */
.manual-optical {
font-variation-settings: "opsz" 12; /* Optimized for 12px */
font-size: 12px;
}
/* Custom axes (font-specific) */
.roboto-flex {
font-family: "Roboto Flex", sans-serif;
font-variation-settings:
"GRAD" 0, /* Grade (stroke contrast) */
"slnt" -10, /* Slant */
"XTRA" 468, /* X-transparent */
"XOPQ" 96, /* X-opaque */
"YOPQ" 79, /* Y-opaque */
"YTLC" 514, /* Y-transparent lowercase */
"YTUC" 712, /* Y-transparent uppercase */
"YTAS" 750, /* Y-transparent ascender */
"YTDE" -203, /* Y-transparent descender */
"YTFI" 738; /* Y-transparent figure height */
}
Note: Variable fonts reduce HTTP requests (single file for multiple weights/widths). Use high-level properties (font-weight, font-stretch) instead of font-variation-settings
when possible for better browser support.
4. Text Layout and Spacing (line-height, letter-spacing)
| Property | Values | Description | Default |
|---|---|---|---|
| line-height | number | length | % |
Vertical spacing between lines | normal (~1.2) |
| letter-spacing | normal | length |
Horizontal space between characters | normal |
| word-spacing | normal | length |
Space between words | normal |
| text-indent | length | % |
First line indentation | 0 |
| text-align | left | right | center | justify | start | end |
Horizontal text alignment | start |
| text-align-last | auto | left | right | center | justify |
Alignment of last line | auto |
| vertical-align | baseline | top | middle | bottom | length | % |
Vertical alignment in inline context | baseline |
| white-space | normal | nowrap | pre | pre-wrap | pre-line |
Whitespace and line break handling | normal |
| tab-size | number | length |
Width of tab character | 8 |
| word-break | normal | break-all | keep-all | break-word |
Line breaking rules for words | normal |
| overflow-wrap | normal | anywhere | break-word |
Break long words to prevent overflow | normal |
| hyphens | none | manual | auto |
Hyphenation for line breaks | manual |
| text-transform | none | uppercase | lowercase | capitalize |
Text case transformation | none |
| text-decoration | line | style | color | thickness |
Underline, overline, line-through | none |
Example: Text spacing and layout
/* Optimal body text readability */
body {
font-size: 1rem;
line-height: 1.6; /* Unitless preferred (relative to font-size) */
letter-spacing: 0.01em; /* Slight tracking */
}
/* Headings with tighter spacing */
h1 {
font-size: 3rem;
line-height: 1.2; /* Tighter for large text */
letter-spacing: -0.02em; /* Negative tracking for large sizes */
}
/* Paragraph first-line indent */
p + p {
text-indent: 2em;
}
/* Justified text with hyphenation */
.justified {
text-align: justify;
hyphens: auto; /* Requires lang attribute */
text-justify: inter-word;
}
/* Prevent text wrapping */
.nowrap {
white-space: nowrap;
}
/* Break long URLs */
.url {
word-break: break-all;
overflow-wrap: break-word;
}
Example: White-space control
/* white-space values comparison */
.normal {
white-space: normal; /* Collapse spaces, wrap lines */
}
.nowrap {
white-space: nowrap; /* Collapse spaces, no wrap */
}
.pre {
white-space: pre; /* Preserve spaces, no wrap */
}
.pre-wrap {
white-space: pre-wrap; /* Preserve spaces, wrap lines */
}
.pre-line {
white-space: pre-line; /* Collapse spaces, preserve line breaks */
}
/* Code block formatting */
pre code {
white-space: pre;
tab-size: 4; /* Tab width: 4 spaces */
}
/* Text overflow with ellipsis */
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
5. Advanced Typography (text-wrap, text-spacing) NEW
| Property | Values | Description | Browser Support |
|---|---|---|---|
| text-wrap | wrap | nowrap | balance | pretty |
Advanced text wrapping modes | Chrome 114+, Safari 17.5+ |
| balance | text-wrap: balance; |
Balanced line lengths (headings) | Chrome 114+ |
| pretty | text-wrap: pretty; |
Avoid orphans/widows (paragraphs) | Chrome 117+ |
| hanging-punctuation | none | first | last | force-end | allow-end |
Hang punctuation outside text box | Safari only |
| text-spacing-trim | normal | space-all | trim-start |
CJK punctuation spacing | Experimental |
| text-autospace | normal | no-autospace |
Spacing between CJK and Latin | Experimental |
Example: Text-wrap balance and pretty
/* Balanced headings (prevents single word on last line) */
h1, h2, h3 {
text-wrap: balance; /* Max 4 lines for performance */
max-inline-size: 40ch; /* Limit width for readability */
}
/* Pretty paragraphs (prevents orphans/widows) */
p {
text-wrap: pretty; /* Avoid single word on last line */
}
/* Traditional approach (still works) */
.traditional {
text-align: justify;
hyphens: auto;
orphans: 3; /* Min lines at bottom of page */
widows: 3; /* Min lines at top of next page */
}
/* Hanging punctuation (Safari) */
blockquote {
hanging-punctuation: first last; /* Hang quotes outside */
}
/* Combined for optimal typography */
article p {
max-inline-size: 65ch; /* Optimal reading width */
text-wrap: pretty;
hyphens: auto;
hanging-punctuation: first last;
}
Example: Advanced text control
/* Multi-line truncation with balanced text */
.card-title {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-wrap: balance; /* Balance truncated text */
}
/* Optimal heading layout */
.heading {
/* Limit width for readability */
max-inline-size: 30ch;
/* Balance line lengths */
text-wrap: balance;
/* Tighter line height */
line-height: 1.1;
/* Negative tracking for large text */
letter-spacing: -0.02em;
/* Hang punctuation outside */
hanging-punctuation: first last;
}
/* Body text optimization */
.body-text {
max-inline-size: 65ch;
text-wrap: pretty; /* Avoid orphans */
line-height: 1.6;
hyphens: auto;
}
/* Preserve manual line breaks */
.poem {
white-space: pre-line; /* Preserve breaks, wrap long lines */
text-wrap: balance;
}
Note:
text-wrap: balance works best for headings (≤4
lines). text-wrap: pretty optimizes paragraphs to avoid
orphans/widows. Limited browser support currently.
6. International Typography Support
| Property | Values | Description | Use Case |
|---|---|---|---|
| writing-mode | horizontal-tb | vertical-rl | vertical-lr |
Text flow direction (horizontal/vertical) | CJK vertical text, Mongolian |
| text-orientation | mixed | upright | sideways |
Character orientation in vertical text | CJK vertical layout |
| direction | ltr | rtl |
Text direction (left-to-right or right-to-left) | Arabic, Hebrew |
| unicode-bidi | normal | embed | isolate | bidi-override |
Bidirectional text algorithm control | Mixed LTR/RTL content |
| text-combine-upright | none | all | digits [n] |
Combine characters in vertical text (tate-chu-yoko) | Japanese vertical numbers |
| font-language-override | normal | string |
Override language-specific glyphs | Use specific OpenType features |
| lang attribute | lang="en" lang="ar" lang="ja" |
HTML language declaration | Required for hyphenation, fonts |
Example: Writing modes and text orientation
/* Vertical text (Japanese, Chinese, Korean) */
.vertical-text {
writing-mode: vertical-rl; /* Right-to-left vertical */
text-orientation: mixed; /* Rotate Latin chars, upright CJK */
}
.vertical-text-lr {
writing-mode: vertical-lr; /* Left-to-right vertical (Mongolian) */
}
/* All characters upright in vertical mode */
.upright {
writing-mode: vertical-rl;
text-orientation: upright;
}
/* Combine numbers in vertical text (tate-chu-yoko) */
.vertical-numbers {
writing-mode: vertical-rl;
}
.number {
text-combine-upright: all; /* Horizontal numbers in vertical text */
}
/* Horizontal text (default) */
.horizontal {
writing-mode: horizontal-tb; /* Top-to-bottom horizontal */
}
Example: RTL and bidirectional text
<!-- HTML lang attribute for proper text handling -->
<html lang="ar" dir="rtl"> <!-- Arabic, right-to-left -->
/* RTL layout */
body {
direction: rtl; /* Right-to-left text flow */
text-align: start; /* Aligns right in RTL */
}
/* LTR content within RTL page */
.ltr-content {
direction: ltr;
unicode-bidi: embed; /* Isolate from parent direction */
}
/* Bidirectional override (force direction) */
.force-ltr {
unicode-bidi: bidi-override;
direction: ltr;
}
/* Isolate mixed content */
.isolated {
unicode-bidi: isolate; /* Prevent direction contamination */
}
/* Logical properties work with writing modes */
.international {
/* Instead of: margin-left, margin-right */
margin-inline-start: 1rem; /* Adapts to LTR/RTL */
margin-inline-end: 1rem;
/* Instead of: margin-top, margin-bottom */
margin-block-start: 1rem; /* Adapts to writing mode */
margin-block-end: 1rem;
}
Example: Language-specific typography
/* Language-specific font stacks */
:lang(en) {
font-family: "Inter", -apple-system, sans-serif;
}
:lang(ja) {
font-family: "Noto Sans JP", "Yu Gothic", "Hiragino Sans", sans-serif;
}
:lang(zh) {
font-family: "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
}
:lang(ar) {
font-family: "Noto Sans Arabic", "Arial", sans-serif;
direction: rtl;
}
:lang(ko) {
font-family: "Noto Sans KR", "Malgun Gothic", sans-serif;
}
/* Auto hyphenation requires lang attribute */
p {
hyphens: auto; /* Only works with proper lang attribute */
}
/* OpenType features for specific languages */
.turkish {
font-language-override: "TRK"; /* Use Turkish-specific glyphs */
}
/* CJK punctuation adjustment */
:lang(ja), :lang(zh) {
text-spacing-trim: space-all; /* Trim CJK punctuation spacing */
}
Typography Best Practices
- Use unitless line-height (1.6) for scalability
- Set
font-display: swapto prevent invisible text (FOIT) - Preload critical fonts with
<link rel="preload"> - Use variable fonts to reduce HTTP requests
- Limit text width to 60-75 characters (45-75ch) for readability
- Use
text-wrap: balancefor headings to avoid orphans - Add
hyphens: autowith properlangattribute - Use logical properties for international layouts
- Match fallback font metrics with
font-size-adjustto reduce layout shift - Use system font stacks for performance:
-apple-system, BlinkMacSystemFont, "Segoe UI"