[{"data":1,"prerenderedAt":1371},["ShallowReactive",2],{"page-\u002Fcss\u002F05-typography-and-text":3},{"id":4,"title":5,"body":6,"description":1364,"extension":1365,"meta":1366,"navigation":861,"path":1367,"seo":1368,"stem":1369,"__hash__":1370},"content\u002Fcss\u002F05-typography-and-text.md","05 — Typography Engine: Font Loading, Variable Fonts & Text Overflow",{"type":7,"value":8,"toc":1344},"minimark",[9,13,17,22,109,119,309,314,364,425,487,491,632,639,670,674,737,782,786,931,938,948,959,1069,1073,1134,1138,1250,1254,1304,1340],[10,11,5],"h1",{"id":12},"_05-typography-engine-font-loading-variable-fonts-text-overflow",[14,15,16],"p",{},"Typography is 80% of web UI. The engine handles it through: font matching (fallback stack), font loading (FOIT\u002FFOUT), text layout (line boxes, wrapping, truncation), and OpenType features (variable font axes). Every \"my text overlaps\" or \"my font flashes\" bug is in one of these stages.",[18,19,21],"h2",{"id":20},"font-stack-the-fallback-chain","Font Stack — the fallback chain",[23,24,26],"code-wrapper",{"language":25},"css",[27,28,32],"pre",{"className":29,"code":30,"language":25,"meta":31,"style":31},"language-css shiki shiki-themes github-light github-dark",":root {\n  \u002F* System stack: 0KB download, native OS UI font. The production default. *\u002F\n  --font-sans: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n  \u002F* The engine tries each in order. The LAST must be a generic family (guaranteed to exist).\n     Quote multi-word names. Generic: serif | sans-serif | monospace | cursive | fantasy | system-ui. *\u002F\n}\n","",[33,34,35,48,55,91,97,103],"code",{"__ignoreMap":31},[36,37,40,44],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"sIsaT",":root",[36,45,47],{"class":46},"ssxIu"," {\n",[36,49,51],{"class":38,"line":50},2,[36,52,54],{"class":53},"sdCPZ","  \u002F* System stack: 0KB download, native OS UI font. The production default. *\u002F\n",[36,56,58,62,65,69,72,75,78,82,85,88],{"class":38,"line":57},3,[36,59,61],{"class":60},"sCrzJ","  --font-sans",[36,63,64],{"class":46},": ",[36,66,68],{"class":67},"snvgF","system-ui",[36,70,71],{"class":46},", ",[36,73,74],{"class":67},"-apple-system",[36,76,77],{"class":46},", BlinkMacSystemFont, ",[36,79,81],{"class":80},"sJ6F3","\"Segoe UI\"",[36,83,84],{"class":46},", Roboto, ",[36,86,87],{"class":67},"sans-serif",[36,89,90],{"class":46},";\n",[36,92,94],{"class":38,"line":93},4,[36,95,96],{"class":53},"  \u002F* The engine tries each in order. The LAST must be a generic family (guaranteed to exist).\n",[36,98,100],{"class":38,"line":99},5,[36,101,102],{"class":53},"     Quote multi-word names. Generic: serif | sans-serif | monospace | cursive | fantasy | system-ui. *\u002F\n",[36,104,106],{"class":38,"line":105},6,[36,107,108],{"class":46},"}\n",[18,110,112,115,116],{"id":111},"font-face-loading-strategy-with-font-display",[33,113,114],{},"@font-face"," — loading strategy with ",[33,117,118],{},"font-display",[23,120,121],{"language":25},[27,122,124],{"className":29,"code":123,"language":25,"meta":31,"style":31},"@font-face {\n  font-family: \"Inter\";\n  src: url(\"inter.woff2\") format(\"woff2-variations\"),  \u002F* variable font: best compression + all weights in one file *\u002F\n       url(\"inter-static.woff2\") format(\"woff2\");        \u002F* fallback: static weight *\u002F\n  font-weight: 100 900;       \u002F* RANGE — declares this face covers all weights (variable font) *\u002F\n  font-style: normal;\n  font-display: swap;         \u002F* show fallback IMMEDIATELY, swap when loaded → no invisible text (FOIT) *\u002F\n  \u002F* font-display values:\n     auto    = browser decides (usually swap)\n     block   = invisible up to 3s, then swap (FOIT for 3s — bad)\n     swap    = fallback immediately, swap when ready (FOUT — recommended for body text)\n     fallback = 100ms invisible, 3s swap window, may not load at all (good for decorative)\n     optional = 100ms invisible, no swap — if it doesn't load fast, never shows (zero layout shift) *\u002F\n  unicode-range: U+0000-00FF;  \u002F* only load Latin glyphs → smaller download for CJK pages *\u002F\n}\n",[33,125,126,133,145,178,203,222,234,251,257,263,269,275,281,287,304],{"__ignoreMap":31},[36,127,128,131],{"class":38,"line":39},[36,129,114],{"class":130},"svdQ7",[36,132,47],{"class":46},[36,134,135,138,140,143],{"class":38,"line":50},[36,136,137],{"class":67},"  font-family",[36,139,64],{"class":46},[36,141,142],{"class":80},"\"Inter\"",[36,144,90],{"class":46},[36,146,147,150,152,155,158,161,164,167,169,172,175],{"class":38,"line":57},[36,148,149],{"class":67},"  src",[36,151,64],{"class":46},[36,153,154],{"class":67},"url",[36,156,157],{"class":46},"(",[36,159,160],{"class":80},"\"inter.woff2\"",[36,162,163],{"class":46},") ",[36,165,166],{"class":67},"format",[36,168,157],{"class":46},[36,170,171],{"class":80},"\"woff2-variations\"",[36,173,174],{"class":46},"),  ",[36,176,177],{"class":53},"\u002F* variable font: best compression + all weights in one file *\u002F\n",[36,179,180,183,185,188,190,192,194,197,200],{"class":38,"line":93},[36,181,182],{"class":67},"       url",[36,184,157],{"class":46},[36,186,187],{"class":80},"\"inter-static.woff2\"",[36,189,163],{"class":46},[36,191,166],{"class":67},[36,193,157],{"class":46},[36,195,196],{"class":80},"\"woff2\"",[36,198,199],{"class":46},");        ",[36,201,202],{"class":53},"\u002F* fallback: static weight *\u002F\n",[36,204,205,208,210,213,216,219],{"class":38,"line":99},[36,206,207],{"class":67},"  font-weight",[36,209,64],{"class":46},[36,211,212],{"class":67},"100",[36,214,215],{"class":67}," 900",[36,217,218],{"class":46},";       ",[36,220,221],{"class":53},"\u002F* RANGE — declares this face covers all weights (variable font) *\u002F\n",[36,223,224,227,229,232],{"class":38,"line":105},[36,225,226],{"class":67},"  font-style",[36,228,64],{"class":46},[36,230,231],{"class":67},"normal",[36,233,90],{"class":46},[36,235,237,240,242,245,248],{"class":38,"line":236},7,[36,238,239],{"class":67},"  font-display",[36,241,64],{"class":46},[36,243,244],{"class":67},"swap",[36,246,247],{"class":46},";         ",[36,249,250],{"class":53},"\u002F* show fallback IMMEDIATELY, swap when loaded → no invisible text (FOIT) *\u002F\n",[36,252,254],{"class":38,"line":253},8,[36,255,256],{"class":53},"  \u002F* font-display values:\n",[36,258,260],{"class":38,"line":259},9,[36,261,262],{"class":53},"     auto    = browser decides (usually swap)\n",[36,264,266],{"class":38,"line":265},10,[36,267,268],{"class":53},"     block   = invisible up to 3s, then swap (FOIT for 3s — bad)\n",[36,270,272],{"class":38,"line":271},11,[36,273,274],{"class":53},"     swap    = fallback immediately, swap when ready (FOUT — recommended for body text)\n",[36,276,278],{"class":38,"line":277},12,[36,279,280],{"class":53},"     fallback = 100ms invisible, 3s swap window, may not load at all (good for decorative)\n",[36,282,284],{"class":38,"line":283},13,[36,285,286],{"class":53},"     optional = 100ms invisible, no swap — if it doesn't load fast, never shows (zero layout shift) *\u002F\n",[36,288,290,293,295,298,301],{"class":38,"line":289},14,[36,291,292],{"class":67},"  unicode-range",[36,294,64],{"class":46},[36,296,297],{"class":67},"U+0000-00FF",[36,299,300],{"class":46},";  ",[36,302,303],{"class":53},"\u002F* only load Latin glyphs → smaller download for CJK pages *\u002F\n",[36,305,307],{"class":38,"line":306},15,[36,308,108],{"class":46},[310,311,313],"h3",{"id":312},"anti-pattern-blocking-font-load-with-invisible-text","Anti-pattern: blocking font load with invisible text",[23,315,316],{"language":25},[27,317,319],{"className":29,"code":318,"language":25,"meta":31,"style":31},"\u002F* ❌ font-display: block (or no font-display on some browsers) → invisible text for up to 3s\n   while the font loads. Users see a blank page. Bounce. *\u002F\n@font-face { font-family: \"BrandFont\"; src: url(\"brand.woff2\"); }\n",[33,320,321,326,331],{"__ignoreMap":31},[36,322,323],{"class":38,"line":39},[36,324,325],{"class":53},"\u002F* ❌ font-display: block (or no font-display on some browsers) → invisible text for up to 3s\n",[36,327,328],{"class":38,"line":50},[36,329,330],{"class":53},"   while the font loads. Users see a blank page. Bounce. *\u002F\n",[36,332,333,335,338,341,343,346,349,352,354,356,358,361],{"class":38,"line":57},[36,334,114],{"class":130},[36,336,337],{"class":46}," { ",[36,339,340],{"class":67},"font-family",[36,342,64],{"class":46},[36,344,345],{"class":80},"\"BrandFont\"",[36,347,348],{"class":46},"; ",[36,350,351],{"class":67},"src",[36,353,64],{"class":46},[36,355,154],{"class":67},[36,357,157],{"class":46},[36,359,360],{"class":80},"\"brand.woff2\"",[36,362,363],{"class":46},"); }\n",[23,365,366],{"language":25},[27,367,369],{"className":29,"code":368,"language":25,"meta":31,"style":31},"\u002F* ✓ font-display: swap → fallback text shows instantly, font swaps when ready.\n   Pair with \u003Clink rel=\"preload\"> for the critical font to minimize the swap window. *\u002F\n@font-face { font-family: \"BrandFont\"; src: url(\"brand.woff2\") format(\"woff2\"); font-display: swap; }\n",[33,370,371,376,381],{"__ignoreMap":31},[36,372,373],{"class":38,"line":39},[36,374,375],{"class":53},"\u002F* ✓ font-display: swap → fallback text shows instantly, font swaps when ready.\n",[36,377,378],{"class":38,"line":50},[36,379,380],{"class":53},"   Pair with \u003Clink rel=\"preload\"> for the critical font to minimize the swap window. *\u002F\n",[36,382,383,385,387,389,391,393,395,397,399,401,403,405,407,409,411,413,416,418,420,422],{"class":38,"line":57},[36,384,114],{"class":130},[36,386,337],{"class":46},[36,388,340],{"class":67},[36,390,64],{"class":46},[36,392,345],{"class":80},[36,394,348],{"class":46},[36,396,351],{"class":67},[36,398,64],{"class":46},[36,400,154],{"class":67},[36,402,157],{"class":46},[36,404,360],{"class":80},[36,406,163],{"class":46},[36,408,166],{"class":67},[36,410,157],{"class":46},[36,412,196],{"class":80},[36,414,415],{"class":46},"); ",[36,417,118],{"class":67},[36,419,64],{"class":46},[36,421,244],{"class":67},[36,423,424],{"class":46},"; }\n",[23,426,428],{"language":427},"html",[27,429,432],{"className":430,"code":431,"language":427,"meta":31,"style":31},"language-html shiki shiki-themes github-light github-dark","\u003C!-- Preload the critical font → starts downloading before CSS parses -->\n\u003Clink rel=\"preload\" href=\"\u002Ffonts\u002Fbrand.woff2\" as=\"font\" type=\"font\u002Fwoff2\" crossorigin>\n",[33,433,434,439],{"__ignoreMap":31},[36,435,436],{"class":38,"line":39},[36,437,438],{"class":53},"\u003C!-- Preload the critical font → starts downloading before CSS parses -->\n",[36,440,441,444,448,451,454,457,460,462,465,468,470,473,476,478,481,484],{"class":38,"line":50},[36,442,443],{"class":46},"\u003C",[36,445,447],{"class":446},"sk71V","link",[36,449,450],{"class":42}," rel",[36,452,453],{"class":46},"=",[36,455,456],{"class":80},"\"preload\"",[36,458,459],{"class":42}," href",[36,461,453],{"class":46},[36,463,464],{"class":80},"\"\u002Ffonts\u002Fbrand.woff2\"",[36,466,467],{"class":42}," as",[36,469,453],{"class":46},[36,471,472],{"class":80},"\"font\"",[36,474,475],{"class":42}," type",[36,477,453],{"class":46},[36,479,480],{"class":80},"\"font\u002Fwoff2\"",[36,482,483],{"class":42}," crossorigin",[36,485,486],{"class":46},">\n",[18,488,490],{"id":489},"variable-fonts-continuous-weight-axes","Variable Fonts — continuous weight axes",[23,492,493],{"language":25},[27,494,496],{"className":29,"code":495,"language":25,"meta":31,"style":31},"@font-face {\n  font-family: \"Inter\";\n  src: url(\"inter.woff2\") format(\"woff2-variations\");\n  font-weight: 100 900;  \u002F* declares the weight RANGE. Without this, font-weight:450 won't work. *\u002F\n  font-stretch: 75% 125%; \u002F* width axis (condensed ↔ expanded) *\u002F\n}\n.heading { font-weight: 580; }  \u002F* any value in the range — no separate font file per weight *\u002F\n.body { font-weight: 430; font-stretch: 100%; }\n\u002F* One file = all weights. Saves KB vs loading 400\u002F500\u002F600\u002F700 separately. *\u002F\n",[33,497,498,504,514,537,552,575,579,600,627],{"__ignoreMap":31},[36,499,500,502],{"class":38,"line":39},[36,501,114],{"class":130},[36,503,47],{"class":46},[36,505,506,508,510,512],{"class":38,"line":50},[36,507,137],{"class":67},[36,509,64],{"class":46},[36,511,142],{"class":80},[36,513,90],{"class":46},[36,515,516,518,520,522,524,526,528,530,532,534],{"class":38,"line":57},[36,517,149],{"class":67},[36,519,64],{"class":46},[36,521,154],{"class":67},[36,523,157],{"class":46},[36,525,160],{"class":80},[36,527,163],{"class":46},[36,529,166],{"class":67},[36,531,157],{"class":46},[36,533,171],{"class":80},[36,535,536],{"class":46},");\n",[36,538,539,541,543,545,547,549],{"class":38,"line":93},[36,540,207],{"class":67},[36,542,64],{"class":46},[36,544,212],{"class":67},[36,546,215],{"class":67},[36,548,300],{"class":46},[36,550,551],{"class":53},"\u002F* declares the weight RANGE. Without this, font-weight:450 won't work. *\u002F\n",[36,553,554,557,559,562,565,568,570,572],{"class":38,"line":99},[36,555,556],{"class":67},"  font-stretch",[36,558,64],{"class":46},[36,560,561],{"class":67},"75",[36,563,564],{"class":130},"%",[36,566,567],{"class":67}," 125",[36,569,564],{"class":130},[36,571,348],{"class":46},[36,573,574],{"class":53},"\u002F* width axis (condensed ↔ expanded) *\u002F\n",[36,576,577],{"class":38,"line":105},[36,578,108],{"class":46},[36,580,581,584,586,589,591,594,597],{"class":38,"line":236},[36,582,583],{"class":42},".heading",[36,585,337],{"class":46},[36,587,588],{"class":67},"font-weight",[36,590,64],{"class":46},[36,592,593],{"class":67},"580",[36,595,596],{"class":46},"; }  ",[36,598,599],{"class":53},"\u002F* any value in the range — no separate font file per weight *\u002F\n",[36,601,602,605,607,609,611,614,616,619,621,623,625],{"class":38,"line":253},[36,603,604],{"class":42},".body",[36,606,337],{"class":46},[36,608,588],{"class":67},[36,610,64],{"class":46},[36,612,613],{"class":67},"430",[36,615,348],{"class":46},[36,617,618],{"class":67},"font-stretch",[36,620,64],{"class":46},[36,622,212],{"class":67},[36,624,564],{"class":130},[36,626,424],{"class":46},[36,628,629],{"class":38,"line":259},[36,630,631],{"class":53},"\u002F* One file = all weights. Saves KB vs loading 400\u002F500\u002F600\u002F700 separately. *\u002F\n",[18,633,635,638],{"id":634},"line-height-why-unitless-is-the-only-correct-choice",[33,636,637],{},"line-height"," — why unitless is the only correct choice",[23,640,641],{"language":25},[27,642,644],{"className":29,"code":643,"language":25,"meta":31,"style":31},"body { line-height: 1.5; }  \u002F* unitless: 1.5 × EACH element's font-size, computed per-element *\u002F\n\u002F* h1 with font-size: 3rem (48px) → line-height: 72px (1.5 × 48). Correct. *\u002F\n",[33,645,646,665],{"__ignoreMap":31},[36,647,648,651,653,655,657,660,662],{"class":38,"line":39},[36,649,650],{"class":446},"body",[36,652,337],{"class":46},[36,654,637],{"class":67},[36,656,64],{"class":46},[36,658,659],{"class":67},"1.5",[36,661,596],{"class":46},[36,663,664],{"class":53},"\u002F* unitless: 1.5 × EACH element's font-size, computed per-element *\u002F\n",[36,666,667],{"class":38,"line":50},[36,668,669],{"class":53},"\u002F* h1 with font-size: 3rem (48px) → line-height: 72px (1.5 × 48). Correct. *\u002F\n",[310,671,673],{"id":672},"anti-pattern-line-height-with-a-unit-causes-overlap","Anti-pattern: line-height with a unit causes overlap",[23,675,676],{"language":25},[27,677,679],{"className":29,"code":678,"language":25,"meta":31,"style":31},"\u002F* ❌ px\u002Fem line-height is a FIXED value inherited as-is → doesn't scale with child font-size *\u002F\nbody { line-height: 24px; font-size: 16px; }\nh1 { font-size: 3rem; }  \u002F* inherits 24px line-height on 48px text → lines OVERLAP (24 \u003C 48) *\u002F\n",[33,680,681,686,716],{"__ignoreMap":31},[36,682,683],{"class":38,"line":39},[36,684,685],{"class":53},"\u002F* ❌ px\u002Fem line-height is a FIXED value inherited as-is → doesn't scale with child font-size *\u002F\n",[36,687,688,690,692,694,696,699,702,704,707,709,712,714],{"class":38,"line":50},[36,689,650],{"class":446},[36,691,337],{"class":46},[36,693,637],{"class":67},[36,695,64],{"class":46},[36,697,698],{"class":67},"24",[36,700,701],{"class":130},"px",[36,703,348],{"class":46},[36,705,706],{"class":67},"font-size",[36,708,64],{"class":46},[36,710,711],{"class":67},"16",[36,713,701],{"class":130},[36,715,424],{"class":46},[36,717,718,720,722,724,726,729,732,734],{"class":38,"line":57},[36,719,10],{"class":446},[36,721,337],{"class":46},[36,723,706],{"class":67},[36,725,64],{"class":46},[36,727,728],{"class":67},"3",[36,730,731],{"class":130},"rem",[36,733,596],{"class":46},[36,735,736],{"class":53},"\u002F* inherits 24px line-height on 48px text → lines OVERLAP (24 \u003C 48) *\u002F\n",[23,738,739],{"language":25},[27,740,742],{"className":29,"code":741,"language":25,"meta":31,"style":31},"\u002F* ✓ unitless line-height is computed per-element → scales with each child's font-size *\u002F\nbody { line-height: 1.5; }\nh1 { font-size: 3rem; }  \u002F* line-height: 72px (1.5 × 48). No overlap. *\u002F\n",[33,743,744,749,763],{"__ignoreMap":31},[36,745,746],{"class":38,"line":39},[36,747,748],{"class":53},"\u002F* ✓ unitless line-height is computed per-element → scales with each child's font-size *\u002F\n",[36,750,751,753,755,757,759,761],{"class":38,"line":50},[36,752,650],{"class":446},[36,754,337],{"class":46},[36,756,637],{"class":67},[36,758,64],{"class":46},[36,760,659],{"class":67},[36,762,424],{"class":46},[36,764,765,767,769,771,773,775,777,779],{"class":38,"line":57},[36,766,10],{"class":446},[36,768,337],{"class":46},[36,770,706],{"class":67},[36,772,64],{"class":46},[36,774,728],{"class":67},[36,776,731],{"class":130},[36,778,596],{"class":46},[36,780,781],{"class":53},"\u002F* line-height: 72px (1.5 × 48). No overlap. *\u002F\n",[18,783,785],{"id":784},"text-truncation-single-and-multi-line","Text Truncation — single and multi-line",[23,787,788],{"language":25},[27,789,791],{"className":29,"code":790,"language":25,"meta":31,"style":31},"\u002F* Single-line ellipsis: the trio. All three required. *\u002F\n.truncate {\n  white-space: nowrap;        \u002F* prevent wrapping *\u002F\n  overflow: hidden;           \u002F* clip what doesn't fit *\u002F\n  text-overflow: ellipsis;    \u002F* show … at the clip point *\u002F\n}\n\n\u002F* Multi-line clamp: -webkit-box is still the standard (line-clamp property is landing but partial) *\u002F\n.clamp-3 {\n  display: -webkit-box;            \u002F* deprecated but the only way line-clamp works *\u002F\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 3;           \u002F* show 3 lines, then … *\u002F\n  overflow: hidden;\n}\n",[33,792,793,798,805,821,837,853,857,863,868,875,891,903,917,927],{"__ignoreMap":31},[36,794,795],{"class":38,"line":39},[36,796,797],{"class":53},"\u002F* Single-line ellipsis: the trio. All three required. *\u002F\n",[36,799,800,803],{"class":38,"line":50},[36,801,802],{"class":42},".truncate",[36,804,47],{"class":46},[36,806,807,810,812,815,818],{"class":38,"line":57},[36,808,809],{"class":67},"  white-space",[36,811,64],{"class":46},[36,813,814],{"class":67},"nowrap",[36,816,817],{"class":46},";        ",[36,819,820],{"class":53},"\u002F* prevent wrapping *\u002F\n",[36,822,823,826,828,831,834],{"class":38,"line":93},[36,824,825],{"class":67},"  overflow",[36,827,64],{"class":46},[36,829,830],{"class":67},"hidden",[36,832,833],{"class":46},";           ",[36,835,836],{"class":53},"\u002F* clip what doesn't fit *\u002F\n",[36,838,839,842,844,847,850],{"class":38,"line":99},[36,840,841],{"class":67},"  text-overflow",[36,843,64],{"class":46},[36,845,846],{"class":67},"ellipsis",[36,848,849],{"class":46},";    ",[36,851,852],{"class":53},"\u002F* show … at the clip point *\u002F\n",[36,854,855],{"class":38,"line":105},[36,856,108],{"class":46},[36,858,859],{"class":38,"line":236},[36,860,862],{"emptyLinePlaceholder":861},true,"\n",[36,864,865],{"class":38,"line":253},[36,866,867],{"class":53},"\u002F* Multi-line clamp: -webkit-box is still the standard (line-clamp property is landing but partial) *\u002F\n",[36,869,870,873],{"class":38,"line":259},[36,871,872],{"class":42},".clamp-3",[36,874,47],{"class":46},[36,876,877,880,882,885,888],{"class":38,"line":265},[36,878,879],{"class":67},"  display",[36,881,64],{"class":46},[36,883,884],{"class":67},"-webkit-box",[36,886,887],{"class":46},";            ",[36,889,890],{"class":53},"\u002F* deprecated but the only way line-clamp works *\u002F\n",[36,892,893,896,898,901],{"class":38,"line":271},[36,894,895],{"class":67},"  -webkit-box-orient",[36,897,64],{"class":46},[36,899,900],{"class":67},"vertical",[36,902,90],{"class":46},[36,904,905,908,910,912,914],{"class":38,"line":277},[36,906,907],{"class":67},"  -webkit-line-clamp",[36,909,64],{"class":46},[36,911,728],{"class":67},[36,913,833],{"class":46},[36,915,916],{"class":53},"\u002F* show 3 lines, then … *\u002F\n",[36,918,919,921,923,925],{"class":38,"line":283},[36,920,825],{"class":67},[36,922,64],{"class":46},[36,924,830],{"class":67},[36,926,90],{"class":46},[36,928,929],{"class":38,"line":289},[36,930,108],{"class":46},[18,932,934,937],{"id":933},"white-space-the-wrapping-control-matrix",[33,935,936],{},"white-space"," — the wrapping control matrix",[23,939,941],{"language":940},"text",[27,942,946],{"className":943,"code":945,"language":940,"meta":31},[944],"language-text","Value      Spaces    Newlines   Wrapping    Use case\nnormal     collapse  collapse   wrap        default body text\nnowrap     collapse  collapse   NO wrap     horizontal scroller items, truncation\npre        preserve  preserve   NO wrap     \u003Cpre>-like code display\npre-wrap   preserve  preserve   wrap        code with long lines that should wrap\npre-line   collapse  preserve   wrap        user input with explicit line breaks\n",[33,947,945],{"__ignoreMap":31},[18,949,951,952,955,956],{"id":950},"word-breaking-overflow-wrap-vs-word-break","Word breaking — ",[33,953,954],{},"overflow-wrap"," vs ",[33,957,958],{},"word-break",[23,960,961],{"language":25},[27,962,964],{"className":29,"code":963,"language":25,"meta":31,"style":31},"\u002F* overflow-wrap: break-word → breaks ONLY when a word would overflow. Gentle, preferred. *\u002F\n.url { overflow-wrap: break-word; }  \u002F* long URL breaks only if it can't fit *\u002F\n\n\u002F* word-break: break-all → breaks ANYWHERE, even mid-word. Aggressive, hurts CJK readability. *\u002F\n.cjk { word-break: break-all; }  \u002F* don't use for Latin text — splits words mid-character *\u002F\n\n\u002F* hyphens: auto → browser hyphenates using dictionary. Needs lang attribute. *\u002F\n[lang=\"en\"] p { hyphens: auto; -webkit-hyphens: auto; }  \u002F* requires \u003Chtml lang=\"en\"> *\u002F\n",[33,965,966,971,990,994,999,1018,1022,1027],{"__ignoreMap":31},[36,967,968],{"class":38,"line":39},[36,969,970],{"class":53},"\u002F* overflow-wrap: break-word → breaks ONLY when a word would overflow. Gentle, preferred. *\u002F\n",[36,972,973,976,978,980,982,985,987],{"class":38,"line":50},[36,974,975],{"class":42},".url",[36,977,337],{"class":46},[36,979,954],{"class":67},[36,981,64],{"class":46},[36,983,984],{"class":67},"break-word",[36,986,596],{"class":46},[36,988,989],{"class":53},"\u002F* long URL breaks only if it can't fit *\u002F\n",[36,991,992],{"class":38,"line":57},[36,993,862],{"emptyLinePlaceholder":861},[36,995,996],{"class":38,"line":93},[36,997,998],{"class":53},"\u002F* word-break: break-all → breaks ANYWHERE, even mid-word. Aggressive, hurts CJK readability. *\u002F\n",[36,1000,1001,1004,1006,1008,1010,1013,1015],{"class":38,"line":99},[36,1002,1003],{"class":42},".cjk",[36,1005,337],{"class":46},[36,1007,958],{"class":67},[36,1009,64],{"class":46},[36,1011,1012],{"class":67},"break-all",[36,1014,596],{"class":46},[36,1016,1017],{"class":53},"\u002F* don't use for Latin text — splits words mid-character *\u002F\n",[36,1019,1020],{"class":38,"line":105},[36,1021,862],{"emptyLinePlaceholder":861},[36,1023,1024],{"class":38,"line":236},[36,1025,1026],{"class":53},"\u002F* hyphens: auto → browser hyphenates using dictionary. Needs lang attribute. *\u002F\n",[36,1028,1029,1032,1035,1037,1040,1043,1045,1047,1050,1052,1055,1057,1060,1062,1064,1066],{"class":38,"line":253},[36,1030,1031],{"class":46},"[",[36,1033,1034],{"class":42},"lang",[36,1036,453],{"class":130},[36,1038,1039],{"class":80},"\"en\"",[36,1041,1042],{"class":46},"] ",[36,1044,14],{"class":446},[36,1046,337],{"class":46},[36,1048,1049],{"class":67},"hyphens",[36,1051,64],{"class":46},[36,1053,1054],{"class":67},"auto",[36,1056,348],{"class":46},[36,1058,1059],{"class":67},"-webkit-hyphens",[36,1061,64],{"class":46},[36,1063,1054],{"class":67},[36,1065,596],{"class":46},[36,1067,1068],{"class":53},"\u002F* requires \u003Chtml lang=\"en\"> *\u002F\n",[18,1070,1072],{"id":1071},"tips-tricks","💡 Tips & Tricks",[1074,1075,1076,1087,1098,1110,1126],"ul",{},[1077,1078,1079,64,1083,1086],"li",{},[1080,1081,1082],"strong",{},"Idiom",[33,1084,1085],{},"font-display: optional"," for non-critical fonts → zero layout shift (if it doesn't load in ~100ms, the browser never swaps). Use for decorative\u002Fbrand fonts where FOUT is worse than never loading.",[1077,1088,1089,64,1091,1094,1095,1097],{},[1080,1090,1082],{},[33,1092,1093],{},"unicode-range"," to subset ",[33,1096,114],{}," → a CJK page only loads the Latin face for Latin text, not the full CJK font. Dramatic KB savings.",[1077,1099,1100,1102,1103,1106,1107,1109],{},[1080,1101,1082],{},": variable fonts (one file, all weights) replace 4–6 static font files. Declare ",[33,1104,1105],{},"font-weight: 100 900"," in ",[33,1108,114],{}," or arbitrary weights won't work.",[1077,1111,1112,64,1114,1117,1118,1121,1122,1125],{},[1080,1113,1082],{},[33,1115,1116],{},"text-wrap: balance"," for headings (evens line lengths, no manual ",[33,1119,1120],{},"\u003Cbr>",") and ",[33,1123,1124],{},"text-wrap: pretty"," for paragraphs (avoids single-word last line). Free typography polish, no JS.",[1077,1127,1128,64,1130,1133],{},[1080,1129,1082],{},[33,1131,1132],{},"font-size: clamp(1rem, 0.9rem + 0.5vw, 1.25rem)"," — fluid body type bounded by rem → respects user font-size setting, no media queries.",[18,1135,1137],{"id":1136},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[1074,1139,1140,1159,1187,1200,1217,1232],{},[1077,1141,1142,1150,1151,1154,1155,1158],{},[1080,1143,1144,1146,1147,1149],{},[33,1145,114],{}," ",[33,1148,351],{}," order matters",": list ",[33,1152,1153],{},"woff2"," first (best compression), then ",[33,1156,1157],{},"woff",". The browser picks the first format it supports.",[1077,1160,1161,64,1173,1176,1177,1180,1181,1183,1184,1186],{},[1080,1162,1163,1165,1166,1169,1170,1172],{},[33,1164,637],{}," with ",[33,1167,1168],{},"em","\u002F",[33,1171,701],{}," inherits as a computed value",[33,1174,1175],{},"line-height: 1.5em"," on body computes to ",[33,1178,1179],{},"24px"," (at 16px font) and inherits as ",[33,1182,1179],{}," — same trap as ",[33,1185,701],{},". Unitless is the only safe choice.",[1077,1188,1189,1195,1196,1199],{},[1080,1190,1191,1194],{},[33,1192,1193],{},"vertical-align"," only works on inline\u002Ftable-cell elements",": it does NOT vertically center block elements. Use flexbox ",[33,1197,1198],{},"align-items: center"," for block centering.",[1077,1201,1202,1208,1209,1212,1213,1216],{},[1080,1203,1204,1207],{},[33,1205,1206],{},"text-align: justify"," creates whitespace \"rivers\"",": especially with short words or narrow columns. Prefer ",[33,1210,1211],{},"left"," for screen readability; ",[33,1214,1215],{},"justify"," for print.",[1077,1218,1219,64,1222,1106,1225,1227,1228,1231],{},[1080,1220,1221],{},"Variable fonts need the axis range declared",[33,1223,1224],{},"font-weight: 100 900;",[33,1226,114],{},". Without it, ",[33,1229,1230],{},"font-weight: 450"," may not work (the browser doesn't know the font supports that weight).",[1077,1233,1234,64,1243,1246,1247,1249],{},[1080,1235,1236,1239,1240,1242],{},[33,1237,1238],{},"hyphens: auto"," requires the ",[33,1241,1034],{}," attribute",[33,1244,1245],{},"\u003Chtml lang=\"en\">"," — without it, the browser can't look up hyphenation rules. Safari needs ",[33,1248,1059],{},".",[18,1251,1253],{"id":1252},"spot-the-bug","🧠 Spot the Bug",[23,1255,1256],{"language":25},[27,1257,1259],{"className":29,"code":1258,"language":25,"meta":31,"style":31},"body { line-height: 20px; font-size: 16px; }\nh1 { font-size: 3rem; }\n",[33,1260,1261,1288],{"__ignoreMap":31},[36,1262,1263,1265,1267,1269,1271,1274,1276,1278,1280,1282,1284,1286],{"class":38,"line":39},[36,1264,650],{"class":446},[36,1266,337],{"class":46},[36,1268,637],{"class":67},[36,1270,64],{"class":46},[36,1272,1273],{"class":67},"20",[36,1275,701],{"class":130},[36,1277,348],{"class":46},[36,1279,706],{"class":67},[36,1281,64],{"class":46},[36,1283,711],{"class":67},[36,1285,701],{"class":130},[36,1287,424],{"class":46},[36,1289,1290,1292,1294,1296,1298,1300,1302],{"class":38,"line":50},[36,1291,10],{"class":446},[36,1293,337],{"class":46},[36,1295,706],{"class":67},[36,1297,64],{"class":46},[36,1299,728],{"class":67},[36,1301,731],{"class":130},[36,1303,424],{"class":46},[1305,1306,1307,1311],"details",{},[1308,1309,1310],"summary",{},"Answer",[14,1312,1313,1316,1317,1319,1320,1322,1323,1326,1327,1330,1331,1333,1334,1336,1337,1339],{},[33,1314,1315],{},"line-height: 20px"," is inherited by ",[33,1318,10],{}," as a fixed 20px value. ",[33,1321,10],{},"'s font-size is ",[33,1324,1325],{},"3rem"," (48px). A 20px line-height on 48px text is smaller than the text → lines overlap (the text is taller than the line box). Fix: ",[33,1328,1329],{},"line-height: 1.5"," (unitless) — computed per-element: ",[33,1332,10],{}," gets 72px (1.5 × 48), body gets 24px (1.5 × 16). Both proportional. A unit (",[33,1335,701],{}," or ",[33,1338,1168],{},") is a fixed inherited value that doesn't scale with the child's font-size.",[1341,1342,1343],"style",{},"html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}",{"title":31,"searchDepth":50,"depth":50,"links":1345},[1346,1347,1351,1352,1356,1357,1359,1361,1362,1363],{"id":20,"depth":50,"text":21},{"id":111,"depth":50,"text":1348,"children":1349},"@font-face — loading strategy with font-display",[1350],{"id":312,"depth":57,"text":313},{"id":489,"depth":50,"text":490},{"id":634,"depth":50,"text":1353,"children":1354},"line-height — why unitless is the only correct choice",[1355],{"id":672,"depth":57,"text":673},{"id":784,"depth":50,"text":785},{"id":933,"depth":50,"text":1358},"white-space — the wrapping control matrix",{"id":950,"depth":50,"text":1360},"Word breaking — overflow-wrap vs word-break",{"id":1071,"depth":50,"text":1072},{"id":1136,"depth":50,"text":1137},{"id":1252,"depth":50,"text":1253},"Font-display strategies, variable font axes, line-height unitless math, single and multi-line truncation, and white-space\u002Fword-break edge cases. Code-first reference for production typography systems.","md",{},"\u002Fcss\u002F05-typography-and-text",{"title":5,"description":1364},"css\u002F05-typography-and-text","JlxPZiWxq9AeLs8Vn9L_5EZD1MtZOgR4wv_ERdh8kxM",1789924644820]