[{"data":1,"prerenderedAt":1240},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap":1233},{"id":4,"title":5,"body":6,"description":1214,"extension":1215,"meta":1216,"navigation":758,"path":1227,"seo":1228,"stem":1231,"__hash__":1232},"content\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap\u002Findex.md","Fixing CLS from Web Font Swap",{"type":7,"value":8,"toc":1206},"minimark",[9,14,47,62,67,104,224,228,237,252,266,276,280,286,535,545,551,633,644,649,837,847,853,857,860,863,1015,1022,1102,1105,1109,1121,1145,1164,1168,1191,1196,1199,1202],[10,11,13],"h1",{"id":12},"how-to-fix-cls-caused-by-a-web-font-swap","How to Fix CLS Caused by a Web Font Swap",[15,16,17,18,23,24,28,29,33,34,38,39,42,43,46],"p",{},"This walkthrough sits under ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002F","Font Loading & Text Rendering Performance",", part of the ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement"," discipline: you switched to ",[30,31,32],"code",{},"font-display: swap"," to stop text from being invisible, LCP improved, and ",[19,35,37],{"href":36},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002F","Cumulative Layout Shift"," went from ",[30,40,41],{},"0.02"," to ",[30,44,45],{},"0.14",". The fallback and the web font disagree about how much space text occupies, and every paragraph re-flows the moment the real face arrives.",[15,48,49,50,53,54,57,58,61],{},"The fix is not to revert. It is to make the fallback occupy the same box, using ",[30,51,52],{},"size-adjust",", ",[30,55,56],{},"ascent-override"," and ",[30,59,60],{},"descent-override"," values you measure rather than copy.",[63,64,66],"h2",{"id":65},"rapid-diagnosis-confirm-the-font-is-the-shift-source","Rapid Diagnosis: Confirm the Font Is the Shift Source",[68,69,70,82,88,94],"ul",{},[71,72,73,77,78,81],"li",{},[74,75,76],"strong",{},"Attribute the shift."," In the Performance panel, expand the ",[74,79,80],{},"Experience"," track and select the shift record. The \"Moved from \u002F Moved to\" entries will name text containers — a heading, a paragraph, an article body — rather than an image or an ad slot.",[71,83,84,87],{},[74,85,86],{},"Correlate the timestamp."," Compare the shift time with the font's response-complete time in the Network panel. A shift landing within a frame or two of the font finishing is definitive.",[71,89,90,93],{},[74,91,92],{},"Test with the font blocked."," Add the font's URL to the Network panel's request-blocking list and reload. If CLS drops to near zero and the page renders entirely in the fallback, the swap is your source.",[71,95,96,99,100,103],{},[74,97,98],{},"Measure the delta directly."," Render one line of representative text in the fallback and in the web font at the same ",[30,101,102],{},"font-size",", and compare the rendered widths. A difference above about 2% will produce a visible re-wrap on a full paragraph.",[105,106,113,114,121,122,121,126,121,130,121,139,121,146,121,152,121,159,121,162,121,166,121,173,121,180,121,184,121,186,121,188,121,190,121,197,121,200,121,204,121,213,121,218,121],"svg",{"xmlns":107,"viewBox":108,"width":109,"role":110,"ariaLabel":111,"style":112},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 244","100%","img","Diagram showing a paragraph rendered in the fallback font occupying three lines and the same paragraph in the web font occupying four lines, pushing the following block down by one line height","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)","\n  ",[115,116],"rect",{"className":117,"x":119,"y":119,"width":109,"height":109,"fill":120},[118],"svg-canvas","0","#ffffff"," ",[123,124,125],"title",{},"Why the swap moves everything below it",[127,128,129],"desc",{},"The fallback renders a paragraph in three lines; the wider web font renders the same text in four lines, so the block beneath it is pushed down by one line height and that displacement is recorded as layout shift.",[115,131],{"x":132,"y":132,"width":133,"height":134,"rx":135,"fill":136,"stroke":137,"style":138},"1","758","242","10","none","currentColor","stroke-opacity:0.18",[140,141,145],"text",{"x":142,"y":143,"fill":137,"style":144},"24","32","font-size:16px;font-weight:700","One extra line is one layout shift",[140,147,151],{"x":148,"y":149,"fill":137,"style":150},"30","60","font-size:11.5px;font-weight:700","Fallback (Arial)",[115,153],{"x":148,"y":154,"width":155,"height":156,"rx":157,"fill":137,"style":158},"70","300","12","2","fill-opacity:0.28",[115,160],{"x":148,"y":161,"width":155,"height":156,"rx":157,"fill":137,"style":158},"90",[115,163],{"x":148,"y":164,"width":165,"height":156,"rx":157,"fill":137,"style":158},"110","210",[115,167],{"x":148,"y":168,"width":155,"height":169,"rx":170,"fill":171,"stroke":171,"style":172},"136","46","5","#0466c8","fill-opacity:0.14;stroke-opacity:0.5",[140,174,179],{"x":175,"y":176,"fill":137,"style":177,"textAnchor":178},"180","164","font-size:11.5px;","middle","next block",[140,181,183],{"x":182,"y":149,"fill":137,"style":150},"430","Web font (Inter, 7% wider)",[115,185],{"x":182,"y":154,"width":155,"height":156,"rx":157,"fill":137,"style":158},[115,187],{"x":182,"y":161,"width":155,"height":156,"rx":157,"fill":137,"style":158},[115,189],{"x":182,"y":164,"width":155,"height":156,"rx":157,"fill":137,"style":158},[115,191],{"x":182,"y":192,"width":193,"height":156,"rx":157,"fill":194,"stroke":195,"style":196},"130","120","#ffc300","#b8860b","fill-opacity:0.6",[115,198],{"x":182,"y":199,"width":155,"height":169,"rx":170,"fill":171,"stroke":171,"style":172},"156",[140,201,179],{"x":202,"y":203,"fill":137,"style":177,"textAnchor":178},"580","184",[205,206],"line",{"x1":207,"y1":168,"x2":208,"y2":199,"stroke":195,"strokeDashArray":209,"style":212},"360","400",[210,211],"4","3","stroke-opacity:0.85",[214,215],"polygon",{"points":216,"fill":195,"style":217},"408,159 396,153 397,163","fill-opacity:0.85",[140,219,223],{"x":220,"y":221,"fill":137,"style":222,"textAnchor":178},"384","216","font-size:11.5px;font-weight:700;","20px displacement × affected area = the CLS you are seeing",[63,225,227],{"id":226},"root-cause-analysis","Root Cause Analysis",[15,229,230,233,234,236],{},[74,231,232],{},"1. Advance-width mismatch."," Every glyph in a font has an advance width. If the web font's average advance is 7% wider than the fallback's, a line that fitted in the fallback overflows into a new line under the web font. This is the dominant term and the one ",[30,235,52],{}," corrects.",[15,238,239,242,243,246,247,57,249,251],{},[74,240,241],{},"2. Ascent and descent mismatch."," The font's ascent and descent metrics determine the height of each line box independently of ",[30,244,245],{},"line-height"," in many layout modes. Two fonts with identical widths can still change a paragraph's total height, moving everything below it — corrected by ",[30,248,56],{},[30,250,60],{},".",[15,253,254,257,258,261,262,265],{},[74,255,256],{},"3. Line-gap inflation."," Some fonts declare a non-zero line gap that the browser adds to every line box. A face with a large ",[30,259,260],{},"lineGap"," produces taller paragraphs than a fallback with none, and ",[30,263,264],{},"line-gap-override: 0%"," normalises it.",[15,267,268,271,272,275],{},[74,269,270],{},"4. The wrong fallback in the stack."," Overrides are tuned for one specific fallback. If your stack ends in a generic ",[30,273,274],{},"sans-serif",", the actual face differs per platform, and a tuning that eliminates the shift on Arial can leave it untouched on Roboto. Naming the fallback explicitly is part of the fix.",[63,277,279],{"id":278},"step-by-step-resolution","Step-by-Step Resolution",[15,281,282,285],{},[74,283,284],{},"1. Measure both faces at the same size."," Use the browser itself as the measuring instrument so you capture the real rendering, including hinting.",[287,288,293],"pre",{"className":289,"code":290,"language":291,"meta":292,"style":292},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Paste into the console on the page whose typography you are matching.\nfunction advance(family, text = 'Handgloves 0123456789 the quick brown fox') {\n  const c = document.createElement('canvas').getContext('2d');\n  c.font = `100px ${family}`;\n  return c.measureText(text).width;\n}\nawait document.fonts.load('100px Inter');\nconst web = advance('Inter'), fb = advance('Arial');\nconsole.log('size-adjust:', ((fb \u002F web) * 100).toFixed(1) + '%');\n\u002F\u002F trade-off: canvas measurement uses the same shaping engine as layout, which is\n\u002F\u002F what you want — but it reflects THIS platform only. Run it on the OS your\n\u002F\u002F field data says most users are on, not on your development machine.\n","javascript","",[30,294,295,303,336,373,393,408,414,433,468,517,523,529],{"__ignoreMap":292},[296,297,299],"span",{"class":205,"line":298},1,[296,300,302],{"class":301},"sjfSM","\u002F\u002F Paste into the console on the page whose typography you are matching.\n",[296,304,306,310,314,318,322,324,326,329,333],{"class":205,"line":305},2,[296,307,309],{"class":308},"sPARh","function",[296,311,313],{"class":312},"smZ65"," advance",[296,315,317],{"class":316},"saISM","(",[296,319,321],{"class":320},"sQw3B","family",[296,323,53],{"class":316},[296,325,140],{"class":320},[296,327,328],{"class":308}," =",[296,330,332],{"class":331},"sZ8jY"," 'Handgloves 0123456789 the quick brown fox'",[296,334,335],{"class":316},") {\n",[296,337,339,342,346,348,351,354,356,359,362,365,367,370],{"class":205,"line":338},3,[296,340,341],{"class":308},"  const",[296,343,345],{"class":344},"sPXB4"," c",[296,347,328],{"class":308},[296,349,350],{"class":316}," document.",[296,352,353],{"class":312},"createElement",[296,355,317],{"class":316},[296,357,358],{"class":331},"'canvas'",[296,360,361],{"class":316},").",[296,363,364],{"class":312},"getContext",[296,366,317],{"class":316},[296,368,369],{"class":331},"'2d'",[296,371,372],{"class":316},");\n",[296,374,376,379,382,385,387,390],{"class":205,"line":375},4,[296,377,378],{"class":316},"  c.font ",[296,380,381],{"class":308},"=",[296,383,384],{"class":331}," `100px ${",[296,386,321],{"class":316},[296,388,389],{"class":331},"}`",[296,391,392],{"class":316},";\n",[296,394,396,399,402,405],{"class":205,"line":395},5,[296,397,398],{"class":308},"  return",[296,400,401],{"class":316}," c.",[296,403,404],{"class":312},"measureText",[296,406,407],{"class":316},"(text).width;\n",[296,409,411],{"class":205,"line":410},6,[296,412,413],{"class":316},"}\n",[296,415,417,420,423,426,428,431],{"class":205,"line":416},7,[296,418,419],{"class":308},"await",[296,421,422],{"class":316}," document.fonts.",[296,424,425],{"class":312},"load",[296,427,317],{"class":316},[296,429,430],{"class":331},"'100px Inter'",[296,432,372],{"class":316},[296,434,436,439,442,444,446,448,451,454,457,459,461,463,466],{"class":205,"line":435},8,[296,437,438],{"class":308},"const",[296,440,441],{"class":344}," web",[296,443,328],{"class":308},[296,445,313],{"class":312},[296,447,317],{"class":316},[296,449,450],{"class":331},"'Inter'",[296,452,453],{"class":316},"), ",[296,455,456],{"class":344},"fb",[296,458,328],{"class":308},[296,460,313],{"class":312},[296,462,317],{"class":316},[296,464,465],{"class":331},"'Arial'",[296,467,372],{"class":316},[296,469,471,474,477,479,482,485,488,491,494,497,499,502,504,506,509,512,515],{"class":205,"line":470},9,[296,472,473],{"class":316},"console.",[296,475,476],{"class":312},"log",[296,478,317],{"class":316},[296,480,481],{"class":331},"'size-adjust:'",[296,483,484],{"class":316},", ((fb ",[296,486,487],{"class":308},"\u002F",[296,489,490],{"class":316}," web) ",[296,492,493],{"class":308},"*",[296,495,496],{"class":344}," 100",[296,498,361],{"class":316},[296,500,501],{"class":312},"toFixed",[296,503,317],{"class":316},[296,505,132],{"class":344},[296,507,508],{"class":316},") ",[296,510,511],{"class":308},"+",[296,513,514],{"class":331}," '%'",[296,516,372],{"class":316},[296,518,520],{"class":205,"line":519},10,[296,521,522],{"class":301},"\u002F\u002F trade-off: canvas measurement uses the same shaping engine as layout, which is\n",[296,524,526],{"class":205,"line":525},11,[296,527,528],{"class":301},"\u002F\u002F what you want — but it reflects THIS platform only. Run it on the OS your\n",[296,530,532],{"class":205,"line":531},12,[296,533,534],{"class":301},"\u002F\u002F field data says most users are on, not on your development machine.\n",[15,536,537,538,540,541,544],{},"Expected outcome: a ",[30,539,52],{}," percentage accurate to a tenth of a percent for the platform you measured on, typically in the ",[30,542,543],{},"95–110%"," range.",[15,546,547,550],{},[74,548,549],{},"2. Read the ascent, descent and line gap."," These come from the font file rather than from rendering. Any font-inspection tool works; the numbers you need are the values divided by units-per-em, expressed as a percentage.",[287,552,556],{"className":553,"code":554,"language":555,"meta":292,"style":292},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","# Ascent, descent and line gap as percentages of the em square.\npython3 - \u003C\u003C'EOF'\nfrom fontTools.ttLib import TTFont\nf = TTFont('inter-var-latin.woff2')\nupm = f['head'].unitsPerEm\nhhea = f['hhea']\nprint(f\"ascent-override: {hhea.ascent \u002F upm * 100:.1f}%\")\nprint(f\"descent-override: {abs(hhea.descent) \u002F upm * 100:.1f}%\")\nprint(f\"line-gap-override: {hhea.lineGap \u002F upm * 100:.1f}%\")\nEOF\n# trade-off: hhea metrics and OS\u002F2 metrics can disagree, and browsers do not all\n# prefer the same table. If your measured line height does not match the\n# computed one, re-read the values from OS\u002F2 sTypoAscender\u002FsTypoDescender.\n","bash",[30,557,558,563,577,582,587,592,597,602,607,612,617,622,627],{"__ignoreMap":292},[296,559,560],{"class":205,"line":298},[296,561,562],{"class":301},"# Ascent, descent and line gap as percentages of the em square.\n",[296,564,565,568,571,574],{"class":205,"line":305},[296,566,567],{"class":320},"python3",[296,569,570],{"class":331}," -",[296,572,573],{"class":308}," \u003C\u003C",[296,575,576],{"class":316},"'EOF'\n",[296,578,579],{"class":205,"line":338},[296,580,581],{"class":331},"from fontTools.ttLib import TTFont\n",[296,583,584],{"class":205,"line":375},[296,585,586],{"class":331},"f = TTFont('inter-var-latin.woff2')\n",[296,588,589],{"class":205,"line":395},[296,590,591],{"class":331},"upm = f['head'].unitsPerEm\n",[296,593,594],{"class":205,"line":410},[296,595,596],{"class":331},"hhea = f['hhea']\n",[296,598,599],{"class":205,"line":416},[296,600,601],{"class":331},"print(f\"ascent-override: {hhea.ascent \u002F upm * 100:.1f}%\")\n",[296,603,604],{"class":205,"line":435},[296,605,606],{"class":331},"print(f\"descent-override: {abs(hhea.descent) \u002F upm * 100:.1f}%\")\n",[296,608,609],{"class":205,"line":470},[296,610,611],{"class":331},"print(f\"line-gap-override: {hhea.lineGap \u002F upm * 100:.1f}%\")\n",[296,613,614],{"class":205,"line":519},[296,615,616],{"class":316},"EOF\n",[296,618,619],{"class":205,"line":525},[296,620,621],{"class":301},"# trade-off: hhea metrics and OS\u002F2 metrics can disagree, and browsers do not all\n",[296,623,624],{"class":205,"line":531},[296,625,626],{"class":301},"# prefer the same table. If your measured line height does not match the\n",[296,628,630],{"class":205,"line":629},13,[296,631,632],{"class":301},"# computed one, re-read the values from OS\u002F2 sTypoAscender\u002FsTypoDescender.\n",[15,634,635,636,640,641,643],{},"Expected outcome: three percentages that describe the ",[637,638,639],"em",{},"web font's"," vertical metrics. Apply them to the fallback face so it matches the web font, and divide each by the ",[30,642,52],{}," factor you just derived.",[15,645,646],{},[74,647,648],{},"3. Declare the adjusted fallback and put it in the stack.",[287,650,654],{"className":651,"code":652,"language":653,"meta":292,"style":292},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","@font-face {\n  font-family: \"Inter Fallback\";\n  src: local(\"Arial\");\n  size-adjust: 107.4%;\n  ascent-override: 90.2%;\n  descent-override: 22.5%;\n  line-gap-override: 0%;\n}\n\n:root { --font-body: \"Inter\", \"Inter Fallback\", Arial, sans-serif; }\nbody { font-family: var(--font-body); }\n\u002F* trade-off: naming Arial explicitly after the adjusted face keeps a sane\n   result if local(\"Arial\") is unavailable, but that unadjusted path will still\n   shift. On platforms where Arial is absent, measure against the face that is\n   actually used and ship a second adjusted fallback. *\u002F\n","css",[30,655,656,664,677,694,709,723,737,750,754,760,792,815,820,825,831],{"__ignoreMap":292},[296,657,658,661],{"class":205,"line":298},[296,659,660],{"class":308},"@font-face",[296,662,663],{"class":316}," {\n",[296,665,666,669,672,675],{"class":205,"line":305},[296,667,668],{"class":344},"  font-family",[296,670,671],{"class":316},": ",[296,673,674],{"class":331},"\"Inter Fallback\"",[296,676,392],{"class":316},[296,678,679,682,684,687,689,692],{"class":205,"line":338},[296,680,681],{"class":344},"  src",[296,683,671],{"class":316},[296,685,686],{"class":344},"local",[296,688,317],{"class":316},[296,690,691],{"class":331},"\"Arial\"",[296,693,372],{"class":316},[296,695,696,699,701,704,707],{"class":205,"line":375},[296,697,698],{"class":344},"  size-adjust",[296,700,671],{"class":316},[296,702,703],{"class":344},"107.4",[296,705,706],{"class":308},"%",[296,708,392],{"class":316},[296,710,711,714,716,719,721],{"class":205,"line":395},[296,712,713],{"class":344},"  ascent-override",[296,715,671],{"class":316},[296,717,718],{"class":344},"90.2",[296,720,706],{"class":308},[296,722,392],{"class":316},[296,724,725,728,730,733,735],{"class":205,"line":410},[296,726,727],{"class":344},"  descent-override",[296,729,671],{"class":316},[296,731,732],{"class":344},"22.5",[296,734,706],{"class":308},[296,736,392],{"class":316},[296,738,739,742,744,746,748],{"class":205,"line":416},[296,740,741],{"class":344},"  line-gap-override",[296,743,671],{"class":316},[296,745,119],{"class":344},[296,747,706],{"class":308},[296,749,392],{"class":316},[296,751,752],{"class":205,"line":435},[296,753,413],{"class":316},[296,755,756],{"class":205,"line":470},[296,757,759],{"emptyLinePlaceholder":758},true,"\n",[296,761,762,765,768,771,773,776,778,780,782,785,787,789],{"class":205,"line":519},[296,763,764],{"class":344},":root",[296,766,767],{"class":316}," { ",[296,769,770],{"class":320},"--font-body",[296,772,671],{"class":316},[296,774,775],{"class":331},"\"Inter\"",[296,777,53],{"class":316},[296,779,674],{"class":331},[296,781,53],{"class":316},[296,783,784],{"class":344},"Arial",[296,786,53],{"class":316},[296,788,274],{"class":344},[296,790,791],{"class":316},"; }\n",[296,793,794,798,800,803,805,808,810,812],{"class":205,"line":525},[296,795,797],{"class":796},"sZBmE","body",[296,799,767],{"class":316},[296,801,802],{"class":344},"font-family",[296,804,671],{"class":316},[296,806,807],{"class":344},"var",[296,809,317],{"class":316},[296,811,770],{"class":320},[296,813,814],{"class":316},"); }\n",[296,816,817],{"class":205,"line":531},[296,818,819],{"class":301},"\u002F* trade-off: naming Arial explicitly after the adjusted face keeps a sane\n",[296,821,822],{"class":205,"line":629},[296,823,824],{"class":301},"   result if local(\"Arial\") is unavailable, but that unadjusted path will still\n",[296,826,828],{"class":205,"line":827},14,[296,829,830],{"class":301},"   shift. On platforms where Arial is absent, measure against the face that is\n",[296,832,834],{"class":205,"line":833},15,[296,835,836],{"class":301},"   actually used and ship a second adjusted fallback. *\u002F\n",[15,838,839,840,843,844,251],{},"Expected outcome: the fallback now renders in the same box as the web font, so the swap changes glyph shapes without changing geometry. Typical residual shift drops from ",[30,841,842],{},"0.05–0.15"," to under ",[30,845,846],{},"0.005",[15,848,849,852],{},[74,850,851],{},"4. Verify the two faces overlay."," Toggle the web font off in DevTools and screenshot; toggle it on and screenshot again at the same scroll position. Diff the two images — text should occupy identical line boxes with only glyph shapes differing.",[63,854,856],{"id":855},"verification","Verification",[15,858,859],{},"Reload with the Layout Shift Regions overlay enabled (Rendering panel) and watch the moment the font resolves. Before the fix you will see blue flashes over every text block; after it, nothing should highlight.",[15,861,862],{},"Quantify with the same observer you used for the baseline, and compare like for like — a cold cache load, the same viewport, the same scroll position:",[287,864,866],{"className":289,"code":865,"language":291,"meta":292,"style":292},"let cls = 0;\nnew PerformanceObserver((l) => {\n  for (const e of l.getEntries()) if (!e.hadRecentInput) cls += e.value;\n}).observe({ type: 'layout-shift', buffered: true });\naddEventListener('visibilitychange', () => console.log('CLS', cls.toFixed(4)),\n  { once: true });\n",[30,867,868,883,904,946,969,1006],{"__ignoreMap":292},[296,869,870,873,876,878,881],{"class":205,"line":298},[296,871,872],{"class":308},"let",[296,874,875],{"class":316}," cls ",[296,877,381],{"class":308},[296,879,880],{"class":344}," 0",[296,882,392],{"class":316},[296,884,885,888,891,894,897,899,902],{"class":205,"line":305},[296,886,887],{"class":308},"new",[296,889,890],{"class":312}," PerformanceObserver",[296,892,893],{"class":316},"((",[296,895,896],{"class":320},"l",[296,898,508],{"class":316},[296,900,901],{"class":308},"=>",[296,903,663],{"class":316},[296,905,906,909,912,914,917,920,923,926,929,932,934,937,940,943],{"class":205,"line":338},[296,907,908],{"class":308},"  for",[296,910,911],{"class":316}," (",[296,913,438],{"class":308},[296,915,916],{"class":344}," e",[296,918,919],{"class":308}," of",[296,921,922],{"class":316}," l.",[296,924,925],{"class":312},"getEntries",[296,927,928],{"class":316},"()) ",[296,930,931],{"class":308},"if",[296,933,911],{"class":316},[296,935,936],{"class":308},"!",[296,938,939],{"class":316},"e.hadRecentInput) cls ",[296,941,942],{"class":308},"+=",[296,944,945],{"class":316}," e.value;\n",[296,947,948,951,954,957,960,963,966],{"class":205,"line":375},[296,949,950],{"class":316},"}).",[296,952,953],{"class":312},"observe",[296,955,956],{"class":316},"({ type: ",[296,958,959],{"class":331},"'layout-shift'",[296,961,962],{"class":316},", buffered: ",[296,964,965],{"class":344},"true",[296,967,968],{"class":316}," });\n",[296,970,971,974,976,979,982,984,987,989,991,994,997,999,1001,1003],{"class":205,"line":395},[296,972,973],{"class":312},"addEventListener",[296,975,317],{"class":316},[296,977,978],{"class":331},"'visibilitychange'",[296,980,981],{"class":316},", () ",[296,983,901],{"class":308},[296,985,986],{"class":316}," console.",[296,988,476],{"class":312},[296,990,317],{"class":316},[296,992,993],{"class":331},"'CLS'",[296,995,996],{"class":316},", cls.",[296,998,501],{"class":312},[296,1000,317],{"class":316},[296,1002,210],{"class":344},[296,1004,1005],{"class":316},")),\n",[296,1007,1008,1011,1013],{"class":205,"line":410},[296,1009,1010],{"class":316},"  { once: ",[296,1012,965],{"class":344},[296,1014,968],{"class":316},[15,1016,1017,1018,1021],{},"Lock it in with a CI assertion tighter than the ",[30,1019,1020],{},"0.1"," threshold, so typography cannot silently reclaim the budget:",[287,1023,1027],{"className":1024,"code":1025,"language":1026,"meta":292,"style":292},"language-json shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","{ \"ci\": { \"assert\": { \"assertions\": {\n  \"cumulative-layout-shift\": [\"error\", { \"maxNumericValue\": 0.05 }],\n  \"font-display\": [\"error\", { \"minScore\": 1 }]\n} } } }\n","json",[30,1028,1029,1051,1076,1097],{"__ignoreMap":292},[296,1030,1031,1034,1037,1040,1043,1045,1048],{"class":205,"line":298},[296,1032,1033],{"class":316},"{ ",[296,1035,1036],{"class":796},"\"ci\"",[296,1038,1039],{"class":316},": { ",[296,1041,1042],{"class":796},"\"assert\"",[296,1044,1039],{"class":316},[296,1046,1047],{"class":796},"\"assertions\"",[296,1049,1050],{"class":316},": {\n",[296,1052,1053,1056,1059,1062,1065,1068,1070,1073],{"class":205,"line":305},[296,1054,1055],{"class":796},"  \"cumulative-layout-shift\"",[296,1057,1058],{"class":316},": [",[296,1060,1061],{"class":331},"\"error\"",[296,1063,1064],{"class":316},", { ",[296,1066,1067],{"class":796},"\"maxNumericValue\"",[296,1069,671],{"class":316},[296,1071,1072],{"class":344},"0.05",[296,1074,1075],{"class":316}," }],\n",[296,1077,1078,1081,1083,1085,1087,1090,1092,1094],{"class":205,"line":338},[296,1079,1080],{"class":796},"  \"font-display\"",[296,1082,1058],{"class":316},[296,1084,1061],{"class":331},[296,1086,1064],{"class":316},[296,1088,1089],{"class":796},"\"minScore\"",[296,1091,671],{"class":316},[296,1093,132],{"class":344},[296,1095,1096],{"class":316}," }]\n",[296,1098,1099],{"class":205,"line":375},[296,1100,1101],{"class":316},"} } } }\n",[15,1103,1104],{},"In the field, confirm with CLS segmented by first-time versus repeat visitors. Repeat visitors have the font cached and never experience the swap at all, so an aggregate p75 can look healthy while every new visitor sees the shift. If the first-visit segment does not improve, your overrides are tuned against a fallback those users do not get.",[63,1106,1108],{"id":1107},"faq","FAQ",[1110,1111,1114,1118],"details",{"className":1112},[1113],"faq-item",[1115,1116,1117],"summary",{},"Can I just copy size-adjust values from a blog post?",[15,1119,1120],{},"Only as a starting point. Published values are specific to one font version, one fallback and one platform's shaping. Font vendors ship metric changes between releases, and your subset may drop glyphs that shifted the average advance. Measuring takes two minutes with the canvas snippet above and gives you a number you can re-derive when the font is updated.",[1110,1122,1124,1127],{"className":1123},[1113],[1115,1125,1126],{},"Does font-display: optional remove the need for overrides?",[15,1128,1129,1130,1133,1134,1137,1138,1141,1142,1144],{},"It removes the shift, not the trade-off. With ",[30,1131,1132],{},"optional"," the browser will not swap a late font in at all, so there is no reflow — but first-time visitors on slow connections then see the fallback for the entire visit. Overrides plus ",[30,1135,1136],{},"swap"," gives you the branded typography ",[637,1139,1140],{},"and"," no shift, at the cost of the measurement work. Choose ",[30,1143,1132],{}," when the face is not worth that work.",[1110,1146,1148,1151],{"className":1147},[1113],[1115,1149,1150],{},"Why is there still a small shift after applying overrides?",[15,1152,1153,1154,1156,1157,1160,1161,1163],{},"Sub-pixel rounding and glyph-specific differences mean the match is never exact; a residual under ",[30,1155,846],{}," is expected and is inside measurement noise. A larger remainder usually means the browser is not using the fallback you tuned — check what ",[30,1158,1159],{},"document.fonts"," actually resolved, and confirm the adjusted face appears in the computed ",[30,1162,802],{}," before the generic keyword.",[63,1165,1167],{"id":1166},"related","Related",[68,1169,1170,1175,1182],{},[71,1171,1172,1174],{},[19,1173,22],{"href":21}," — the full pipeline this fix belongs to, including discovery and subsetting.",[71,1176,1177,1181],{},[19,1178,1180],{"href":1179},"\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffont-display-swap-vs-optional-for-lcp\u002F","font-display: swap vs optional for LCP"," — decide whether to eliminate the swap instead of matching it.",[71,1183,1184,1187,1188,1190],{},[19,1185,1186],{"href":36},"Reducing Cumulative Layout Shift"," — the other shift sources that share the same ",[30,1189,1020],{}," budget.",[1192,1193,1195],"script",{"type":1194},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Eliminate layout shift caused by a web font swap\",\n  \"description\": \"Measure the advance-width and vertical metrics of a web font, derive size-adjust and ascent\u002Fdescent overrides for the fallback, and verify the residual shift is negligible.\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Measure both faces\", \"text\": \"Use canvas measureText to compare the advance width of the web font and the fallback at the same size.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Read vertical metrics\", \"text\": \"Extract ascent, descent and line gap from the font file as percentages of the em square.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Declare the adjusted fallback\", \"text\": \"Create a local fallback face with size-adjust and ascent\u002Fdescent overrides and place it in the font stack.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Verify the overlay\", \"text\": \"Screenshot with and without the web font and confirm identical line boxes, then assert a CLS budget in CI.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap\u002F#verification\" }\n  ]\n}\n",[1192,1197,1198],{"type":1194},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Fix CLS Caused by a Web Font Swap\",\n  \"description\": \"Measure and derive size-adjust, ascent-override and descent-override values so a font swap no longer moves the layout.\",\n  \"datePublished\": \"2026-08-01\",\n  \"dateModified\": \"2026-08-01\",\n  \"author\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"publisher\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"mainEntityOfPage\": { \"@type\": \"WebPage\", \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap\u002F\" }\n}\n",[1192,1200,1201],{"type":1194},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"Core Web Vitals & Measurement\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Font Loading & Text Rendering Performance\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"Fixing CLS from Web Font Swap\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap\u002F\" }\n  ]\n}\n",[1203,1204,1205],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}",{"title":292,"searchDepth":305,"depth":305,"links":1207},[1208,1209,1210,1211,1212,1213],{"id":65,"depth":305,"text":66},{"id":226,"depth":305,"text":227},{"id":278,"depth":305,"text":279},{"id":855,"depth":305,"text":856},{"id":1107,"depth":305,"text":1108},{"id":1166,"depth":305,"text":1167},"Derive metric overrides from measurement instead of copying them: the exact workflow for eliminating the reflow when a web font replaces its fallback.","md",{"slug":1217,"type":1218,"breadcrumb":1219,"datePublished":1226,"dateModified":1226},"fixing-cls-from-web-font-swap","article",[1220,1222,1223,1224],{"name":1221,"url":487},"Home",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1225},"\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap\u002F","2026-08-01","\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap",{"title":1229,"description":1230},"How to Fix CLS Caused by Web Font Swap","Measure the layout shift a font swap causes, derive size-adjust and ascent-override values for your fallback, and verify the residual shift is under 0.005.","core-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffixing-cls-from-web-font-swap\u002Findex","wfxJIzrD9zzZelWTk7II-pSGgBZ_Xh5KCM-V07iDQWU",[1234,1237],{"title":22,"path":1235,"stem":1236},"\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance","core-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Findex",{"title":1180,"path":1238,"stem":1239},"\u002Fcore-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffont-display-swap-vs-optional-for-lcp","core-web-vitals-measurement\u002Ffont-loading-and-text-rendering-performance\u002Ffont-display-swap-vs-optional-for-lcp\u002Findex",1785622406651]