[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-variable-fonts-one-file-many-weights":44,"search-suggestions":60,"quiz-article-variable-fonts-one-file-many-weights":108,"path-membership-variable-fonts-one-file-many-weights":290},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a1af7a-c600-74f8-a9b3-90622b459ca7","variable-fonts-one-file-many-weights","PRACTICE_QUIZ","Variable fonts — one file, every weight","Check what stuck about variable fonts: how they differ from static font files, how to declare one, and when the trade-off actually pays off.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},7,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,85,89,93,96,100,104],{"slug":62,"name":63,"articles":64},"webdev","Webdev",177,{"slug":66,"name":67,"articles":68},"javascript","Javascript",142,{"slug":70,"name":71,"articles":72},"frontend","Frontend",98,{"slug":74,"name":75,"articles":76},"tutorial","Tutorial",72,{"slug":78,"name":79,"articles":80},"css","Css",53,{"slug":82,"name":83,"articles":84},"performance","Performance",31,{"slug":86,"name":87,"articles":88},"typescript","Typescript",25,{"slug":90,"name":91,"articles":92},"react","React",20,{"slug":94,"name":95,"articles":92},"programming","Programming",{"slug":97,"name":98,"articles":99},"browser","Browser",18,{"slug":101,"name":102,"articles":103},"node","Node",16,{"slug":105,"name":106,"articles":107},"html","Html",12,{"id":109,"slug":46,"title":110,"subtitle":52,"excerpt":111,"coverUrl":112,"locale":13,"readingMinutes":113,"publishedAt":114,"viewCount":115,"likeCount":19,"commentCount":19,"author":116,"vertical":121,"topic":122,"tags":124,"_count":129,"playground":131,"body":133,"bodyMd":276,"seo":277,"translationGroupId":280,"series":52,"podcastUrl":52,"verticalId":5,"thread":281,"assessments":283,"translations":286,"quiz":288},"01a1af7a-c595-77be-8e0a-dea27ce0db13","I Was Shipping Five Font Files. One Variable Font Replaced Them All.","Regular, Medium, Semibold, Bold, Bold Italic — five @font-face blocks, five network requests, just to render a design system's type scale. A single variable font file replaces all of them, and it's been supported everywhere since 2018.","\u002Fmedia\u002Fcovers\u002Fvariable-fonts-one-file-many-weights.png",6,"2026-11-15T12:10:51.143Z",29,{"id":117,"name":118,"username":119,"avatarUrl":52,"headline":120},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":78,"name":123},"CSS",[125,126,127,128],{"slug":78,"name":79,"color":52},{"slug":62,"name":63,"color":52},{"slug":70,"name":71,"color":52},{"slug":82,"name":83,"color":52},{"assessments":130},1,{"slug":46,"title":132},"Variable fonts — interactive playground",{"blocks":134,"version":130},[135,139,142,146,149,154,157,160,164,167,170,174,177,181,184,187,190,194,197,200,203,206,209,212,215,218,221,224,227,230,233,236,239,242,245,248,251,258,261,264,267,270],{"id":136,"html":137,"type":138},"b1","\u003Cp>Open the network tab on a site with a half-decent type system and count the font requests. Four is normal. Six isn&#39;t rare. Every one of them is a separate \u003Ccode>.woff2\u003C\u002Fcode> file for a separate weight — Regular, Medium, Semibold, Bold — sitting in the critical path before the browser can paint text in anything but a fallback font.\u003C\u002Fp>","paragraph",{"id":140,"html":141,"type":138},"b2","\u003Cp>Our design system had five: four weights plus a bold italic for pull-quotes. Nobody had decided that on purpose. It accumulated — a designer added Semibold for one component, Bold Italic followed for another, and eighteen months later the font folder looked like a weight had been added every time someone liked how it looked in Figma.\u003C\u002Fp>",{"id":143,"html":144,"text":144,"type":145,"level":31},"b3","The fix everyone reaches for first","heading",{"id":147,"html":148,"type":138},"b4","\u003Cp>When this shows up in a performance audit, the fix is almost always the same: cut weights. Drop from five down to three. Merge Semibold into Medium. Ship Bold Italic as a \u003Ccode>font-style: oblique\u003C\u002Fcode> of regular Bold instead of a dedicated file.\u003C\u002Fp>",{"id":150,"code":151,"type":152,"language":78,"highlight":153},"b5","@font-face {\n  font-family: \"Inter\";\n  src: url(\"\u002Ffonts\u002Finter-regular.woff2\") format(\"woff2\");\n  font-weight: 400;\n}\n@font-face {\n  font-family: \"Inter\";\n  src: url(\"\u002Ffonts\u002Finter-medium.woff2\") format(\"woff2\");\n  font-weight: 500;\n}\n@font-face {\n  font-family: \"Inter\";\n  src: url(\"\u002Ffonts\u002Finter-bold.woff2\") format(\"woff2\");\n  font-weight: 700;\n}","code",[],{"id":155,"html":156,"type":138},"b6","\u003Cp>This genuinely helps — three requests beat five. It also doesn&#39;t touch the actual shape of the problem: every weight you keep is still its own file, its own request, its own round trip. Cut too aggressively and you lose the weight your design actually needs; cut too little and you&#39;re back where you started with a slightly smaller number.\u003C\u002Fp>",{"id":158,"html":159,"type":138},"b7","\u003Cp>\u003Cstrong>Before you keep reading — guess how many of those weights the browser actually downloads on a page that only uses Regular body text and one Bold heading.\u003C\u002Fstrong> The honest (and slightly surprising) answer is: two. Browsers don&#39;t fetch an \u003Ccode>@font-face\u003C\u002Fcode> file the moment it&#39;s declared — the CSS Fonts spec requires user agents to download only the faces a page&#39;s rendered text actually calls for, so Medium, Semibold, and Bold Italic never leave the server for this particular page. That&#39;s the one thing &quot;cut weights&quot; already gets right, mostly by accident. What it doesn&#39;t fix: those two downloads are still two separate round trips, and the moment any other page in the same design system reaches for a third weight — a Semibold stat callout, a Bold Italic pull-quote — that page pays for a third file, one request at a time, because nothing about a static setup can serve more than the single weight baked into each one.\u003C\u002Fp>",{"id":161,"html":162,"text":163,"type":145,"level":31},"b8","It&#39;s not about how many weights — it&#39;s about how many files","It's not about how many weights — it's about how many files",{"id":165,"html":166,"type":138},"b9","\u003Cp>Here&#39;s what the &quot;cut weights&quot; fix never addresses: a static font file is a fixed snapshot. One file draws letterforms at exactly 700 and nothing else. Want 650 for a hover state that should feel &quot;almost bold but not quite&quot;? You can&#39;t — 650 doesn&#39;t exist unless you shipped a sixth file for it. The weight scale looks continuous in your design tool and is actually a handful of hardcoded stops in production.\u003C\u002Fp>",{"id":168,"html":169,"type":138},"b10","\u003Cp>A \u003Cstrong>variable font\u003C\u002Fstrong> isn&#39;t a set of snapshots. It&#39;s one file that encodes the whole range as interpolation data — the letterforms at 100 and the letterforms at 900, with the browser computing everything in between on the fly. Ask for \u003Ccode>font-weight: 538\u003C\u002Fcode> and you get a real, rendered 538, not a browser fallback or a rounded guess.\u003C\u002Fp>",{"id":171,"code":172,"type":152,"language":78,"highlight":173},"b11","@font-face {\n  font-family: \"Inter\";\n  src: url(\"\u002Ffonts\u002Finter-variable.woff2\") format(\"woff2-variations\");\n  font-weight: 100 900; \u002F* a range, not a single value *\u002F\n  font-stretch: 75% 125%;\n}",[],{"id":175,"html":176,"type":138},"b12","\u003Cp>That&#39;s the whole type system. One \u003Ccode>@font-face\u003C\u002Fcode> block. One network request. Every weight from thin to black, plus every stop in between, available from the same file:\u003C\u002Fp>",{"id":178,"code":179,"type":152,"language":78,"highlight":180},"b13","h1   { font-weight: 700; }\nh2   { font-weight: 600; }\nbody { font-weight: 400; }\n.btn:hover { font-weight: 538; } \u002F* yes, this is a valid value now *\u002F",[],{"id":182,"html":183,"type":138},"b14","\u003Cp>Five requests become one. The five-weight design system from the top of this post could have shipped as a single file from day one — nobody needed to decide anything about which weights to keep, because the weight is no longer a file, it&#39;s a number.\u003C\u002Fp>",{"id":185,"html":186,"text":186,"type":145,"level":31},"b15","When font-variation-settings is the wrong tool",{"id":188,"html":189,"type":138},"b16","\u003Cp>Variable fonts expose more than weight. The OpenType spec registers five standard axes: weight (\u003Ccode>wght\u003C\u002Fcode>), width (\u003Ccode>wdth\u003C\u002Fcode>), slant (\u003Ccode>slnt\u003C\u002Fcode>), italic (\u003Ccode>ital\u003C\u002Fcode>), and optical size (\u003Ccode>opsz\u003C\u002Fcode>) — plus whatever custom axes an individual typeface adds on top (a &quot;grade&quot; axis some foundries ship for tuning contrast in dark mode is a common one). The low-level way to reach any axis by its tag is \u003Ccode>font-variation-settings\u003C\u002Fcode>:\u003C\u002Fp>",{"id":191,"code":192,"type":152,"language":78,"highlight":193},"b17",".headline {\n  font-variation-settings: \"wght\" 650, \"opsz\" 32;\n}",[],{"id":195,"html":196,"type":138},"b18","\u003Cp>It works, but it&#39;s the wrong default for four of those five registered axes — \u003Ccode>wght\u003C\u002Fcode>, \u003Ccode>wdth\u003C\u002Fcode>, \u003Ccode>ital\u003C\u002Fcode>, and \u003Ccode>slnt\u003C\u002Fcode> already have real CSS properties (\u003Ccode>font-weight\u003C\u002Fcode>, \u003Ccode>font-stretch\u003C\u002Fcode>, \u003Ccode>font-style: italic\u003C\u002Fcode> \u002F \u003Ccode>oblique &lt;angle&gt;\u003C\u002Fcode>) that do the same job and play better with the rest of CSS: they animate and transition the way you&#39;d expect, they interact correctly with \u003Ccode>font-weight: bolder\u003C\u002Fcode>\u002F\u003Ccode>lighter\u003C\u002Fcode> inheritance, and they don&#39;t silently override each other the way two \u003Ccode>font-variation-settings\u003C\u002Fcode> declarations on the same element do. \u003Ccode>opsz\u003C\u002Fcode> is the odd one out — CSS only gives you an on\u002Foff switch for it (\u003Ccode>font-optical-sizing: auto\u003C\u002Fcode>, which is also the default), not a property that sets a specific number — so pinning an exact optical-size value, or reaching a font&#39;s own custom axes, is what \u003Ccode>font-variation-settings\u003C\u002Fcode> is actually for.\u003C\u002Fp>",{"id":198,"html":199,"type":138},"b19","\u003C!-- playground:start -->",{"id":201,"html":202,"text":202,"type":145,"level":31},"b20","🎮 Try it yourself",{"id":204,"html":205,"type":138},"b21","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvariable-fonts-one-file-many-weights\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":207,"html":208,"type":138},"b22","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":210,"html":211,"type":138},"b23","\u003C!-- playground:end -->",{"id":213,"html":214,"text":214,"type":145,"level":31},"b24","The part worth being honest about",{"id":216,"html":217,"type":138},"b25","\u003Cp>A variable font file is not automatically smaller than a single static one. It has to encode interpolation data for the entire range it covers, so a variable file is often larger than any \u003Cem>one\u003C\u002Fem> static weight on its own — sometimes close to the size of two or three static files combined, especially once a large character set (Cyrillic, Greek, a wide set of Latin diacritics) is involved. The win isn&#39;t &quot;smaller file.&quot; It&#39;s &quot;one request instead of several,&quot; and that trade only pays off once you&#39;re actually using three or more weights. A page that only ever renders Regular text has nothing to gain from swapping to a variable font — a single static \u003Ccode>.woff2\u003C\u002Fcode> is still the smallest, fastest thing you can ship.\u003C\u002Fp>",{"id":219,"html":220,"type":138},"b26","\u003Cp>The other honest caveat: this isn&#39;t new. Safari and Chrome shipped variable font support in 2017; Firefox and Edge followed in 2018. There&#39;s no legacy-browser excuse here — the fix has been sitting in every evergreen browser for the better part of a decade, available the moment you swap the font file and the \u003Ccode>@font-face\u003C\u002Fcode> block that points at it.\u003C\u002Fp>",{"id":222,"html":223,"text":223,"type":145,"level":31},"b27","What changed",{"id":225,"html":226,"type":138},"b28","\u003Cp>Our five-weight type system is now one file and one \u003Ccode>@font-face\u003C\u002Fcode> block, and the button hover state finally gets the in-between weight it always should have had instead of jumping straight from 400 to 700. Nothing about the design changed. The number of HTTP requests it took to render it did.\u003C\u002Fp>",{"id":228,"html":229,"type":138},"b29","\u003Cp>Open your own network tab and count the font requests on something you shipped this year. If it&#39;s more than two, how many of those files are actually different weights of the exact same typeface — and what would happen to your render time if they were one?\u003C\u002Fp>",{"id":231,"html":232,"type":138},"b30","\u003C!-- quiz:start -->",{"id":234,"html":235,"text":235,"type":145,"level":31},"b31","🧠 Test yourself",{"id":237,"html":238,"type":138},"b32","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvariable-fonts-one-file-many-weights\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":240,"html":241,"type":138},"b33","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":243,"html":244,"type":138},"b34","\u003C!-- quiz:end -->",{"id":246,"html":247,"type":138},"b35","\u003C!-- related:start -->",{"id":249,"html":250,"text":250,"type":145,"level":31},"b36","📚 Read next",{"id":252,"type":253,"items":254,"ordered":18},"b37","list",[255,256,257],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ffile-system-access-api-real-file-read-write\">Your &#39;Save&#39; Button Makes Copies. The File System Access API Doesn&#39;t.\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcontent-visibility-skip-offscreen-rendering\">Your browser renders everything, even what you can&#39;t see — \u003Ccode>content-visibility: auto\u003C\u002Fcode> fixes that\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-will-change-performance-hint\">You Added \u003Ccode>will-change\u003C\u002Fcode> to Fix the Jank. You Made It Worse.\u003C\u002Fa>",{"id":259,"html":260,"type":138},"b38","\u003C!-- related:end -->",{"id":262,"type":263},"b39","divider",{"id":265,"html":266,"type":138},"b40","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":268,"html":269,"type":138},"b41","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":271,"type":253,"items":272,"ordered":18},"b42",[273,274,275],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","Open the network tab on a site with a half-decent type system and count the font requests. Four is normal. Six isn't rare. Every one of them is a separate `.woff2` file for a separate weight — Regular, Medium, Semibold, Bold — sitting in the critical path before the browser can paint text in anything but a fallback font.\n\nOur design system had five: four weights plus a bold italic for pull-quotes. Nobody had decided that on purpose. It accumulated — a designer added Semibold for one component, Bold Italic followed for another, and eighteen months later the font folder looked like a weight had been added every time someone liked how it looked in Figma.\n\n## The fix everyone reaches for first\n\nWhen this shows up in a performance audit, the fix is almost always the same: cut weights. Drop from five down to three. Merge Semibold into Medium. Ship Bold Italic as a `font-style: oblique` of regular Bold instead of a dedicated file.\n\n```css\n@font-face {\n  font-family: \"Inter\";\n  src: url(\"\u002Ffonts\u002Finter-regular.woff2\") format(\"woff2\");\n  font-weight: 400;\n}\n@font-face {\n  font-family: \"Inter\";\n  src: url(\"\u002Ffonts\u002Finter-medium.woff2\") format(\"woff2\");\n  font-weight: 500;\n}\n@font-face {\n  font-family: \"Inter\";\n  src: url(\"\u002Ffonts\u002Finter-bold.woff2\") format(\"woff2\");\n  font-weight: 700;\n}\n```\n\nThis genuinely helps — three requests beat five. It also doesn't touch the actual shape of the problem: every weight you keep is still its own file, its own request, its own round trip. Cut too aggressively and you lose the weight your design actually needs; cut too little and you're back where you started with a slightly smaller number.\n\n**Before you keep reading — guess how many of those weights the browser actually downloads on a page that only uses Regular body text and one Bold heading.** The honest (and slightly surprising) answer is: two. Browsers don't fetch an `@font-face` file the moment it's declared — the CSS Fonts spec requires user agents to download only the faces a page's rendered text actually calls for, so Medium, Semibold, and Bold Italic never leave the server for this particular page. That's the one thing \"cut weights\" already gets right, mostly by accident. What it doesn't fix: those two downloads are still two separate round trips, and the moment any other page in the same design system reaches for a third weight — a Semibold stat callout, a Bold Italic pull-quote — that page pays for a third file, one request at a time, because nothing about a static setup can serve more than the single weight baked into each one.\n\n## It's not about how many weights — it's about how many files\n\nHere's what the \"cut weights\" fix never addresses: a static font file is a fixed snapshot. One file draws letterforms at exactly 700 and nothing else. Want 650 for a hover state that should feel \"almost bold but not quite\"? You can't — 650 doesn't exist unless you shipped a sixth file for it. The weight scale looks continuous in your design tool and is actually a handful of hardcoded stops in production.\n\nA **variable font** isn't a set of snapshots. It's one file that encodes the whole range as interpolation data — the letterforms at 100 and the letterforms at 900, with the browser computing everything in between on the fly. Ask for `font-weight: 538` and you get a real, rendered 538, not a browser fallback or a rounded guess.\n\n```css\n@font-face {\n  font-family: \"Inter\";\n  src: url(\"\u002Ffonts\u002Finter-variable.woff2\") format(\"woff2-variations\");\n  font-weight: 100 900; \u002F* a range, not a single value *\u002F\n  font-stretch: 75% 125%;\n}\n```\n\nThat's the whole type system. One `@font-face` block. One network request. Every weight from thin to black, plus every stop in between, available from the same file:\n\n```css\nh1   { font-weight: 700; }\nh2   { font-weight: 600; }\nbody { font-weight: 400; }\n.btn:hover { font-weight: 538; } \u002F* yes, this is a valid value now *\u002F\n```\n\nFive requests become one. The five-weight design system from the top of this post could have shipped as a single file from day one — nobody needed to decide anything about which weights to keep, because the weight is no longer a file, it's a number.\n\n## When font-variation-settings is the wrong tool\n\nVariable fonts expose more than weight. The OpenType spec registers five standard axes: weight (`wght`), width (`wdth`), slant (`slnt`), italic (`ital`), and optical size (`opsz`) — plus whatever custom axes an individual typeface adds on top (a \"grade\" axis some foundries ship for tuning contrast in dark mode is a common one). The low-level way to reach any axis by its tag is `font-variation-settings`:\n\n```css\n.headline {\n  font-variation-settings: \"wght\" 650, \"opsz\" 32;\n}\n```\n\nIt works, but it's the wrong default for four of those five registered axes — `wght`, `wdth`, `ital`, and `slnt` already have real CSS properties (`font-weight`, `font-stretch`, `font-style: italic` \u002F `oblique \u003Cangle>`) that do the same job and play better with the rest of CSS: they animate and transition the way you'd expect, they interact correctly with `font-weight: bolder`\u002F`lighter` inheritance, and they don't silently override each other the way two `font-variation-settings` declarations on the same element do. `opsz` is the odd one out — CSS only gives you an on\u002Foff switch for it (`font-optical-sizing: auto`, which is also the default), not a property that sets a specific number — so pinning an exact optical-size value, or reaching a font's own custom axes, is what `font-variation-settings` is actually for.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvariable-fonts-one-file-many-weights\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The part worth being honest about\n\nA variable font file is not automatically smaller than a single static one. It has to encode interpolation data for the entire range it covers, so a variable file is often larger than any *one* static weight on its own — sometimes close to the size of two or three static files combined, especially once a large character set (Cyrillic, Greek, a wide set of Latin diacritics) is involved. The win isn't \"smaller file.\" It's \"one request instead of several,\" and that trade only pays off once you're actually using three or more weights. A page that only ever renders Regular text has nothing to gain from swapping to a variable font — a single static `.woff2` is still the smallest, fastest thing you can ship.\n\nThe other honest caveat: this isn't new. Safari and Chrome shipped variable font support in 2017; Firefox and Edge followed in 2018. There's no legacy-browser excuse here — the fix has been sitting in every evergreen browser for the better part of a decade, available the moment you swap the font file and the `@font-face` block that points at it.\n\n## What changed\n\nOur five-weight type system is now one file and one `@font-face` block, and the button hover state finally gets the in-between weight it always should have had instead of jumping straight from 400 to 700. Nothing about the design changed. The number of HTTP requests it took to render it did.\n\nOpen your own network tab and count the font requests on something you shipped this year. If it's more than two, how many of those files are actually different weights of the exact same typeface — and what would happen to your render time if they were one?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvariable-fonts-one-file-many-weights\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [Your 'Save' Button Makes Copies. The File System Access API Doesn't.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ffile-system-access-api-real-file-read-write)\n- [Your browser renders everything, even what you can't see — `content-visibility: auto` fixes that](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcontent-visibility-skip-offscreen-rendering)\n- [You Added `will-change` to Fix the Jank. You Made It Worse.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-will-change-performance-hint)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":110,"canonical":278,"description":279},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvariable-fonts-one-file-many-weights","Regular, Medium, Semibold, Bold, Bold Italic — five @font-face blocks, five network requests, just to render a design system's type scale. A single variable font file replaces all ","01a1af7a-c596-725d-a2c3-fb206f18d926",{"id":282,"locked":18},"01a1af7a-c5cb-766b-b6e9-9b3cbc15720a",[284],{"id":45,"slug":46,"title":48,"_count":285},{"questions":51},[287],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":289,"questionCount":51},{"questions":51},[]]