[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-css-clamp-fluid-typography":44,"search-suggestions":60,"quiz-article-css-clamp-fluid-typography":105},[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},"01a0042a-85ad-75ae-bd1f-c727da9a8f79","css-clamp-fluid-typography","PRACTICE_QUIZ","clamp() and fluid sizing","Eight questions on what clamp(MIN, PREFERRED, MAX) actually does, the accessibility trap in the vw-only formula, and where it stops being the right tool.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},8,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,84,88,92,96,99,102],{"slug":62,"name":63,"articles":64},"webdev","Webdev",58,{"slug":66,"name":67,"articles":68},"frontend","Frontend",52,{"slug":70,"name":71,"articles":72},"javascript","Javascript",50,{"slug":74,"name":75,"articles":76},"css","Css",23,{"slug":78,"name":79,"articles":80},"typescript","Typescript",10,{"slug":82,"name":83,"articles":51},"performance","Performance",{"slug":85,"name":86,"articles":87},"grammar","Grammar",6,{"slug":89,"name":90,"articles":91},"react","React",5,{"slug":93,"name":94,"articles":95},"tutorial","Tutorial",4,{"slug":97,"name":98,"articles":95},"node","Node",{"slug":100,"name":101,"articles":43},"ai","Ai",{"slug":103,"name":104,"articles":43},"html","Html",{"id":106,"slug":46,"title":107,"subtitle":52,"excerpt":108,"coverUrl":109,"locale":13,"readingMinutes":95,"publishedAt":110,"viewCount":111,"likeCount":19,"commentCount":19,"author":112,"vertical":117,"topic":118,"tags":120,"_count":127,"playground":129,"body":131,"bodyMd":260,"seo":261,"translationGroupId":263,"thread":264,"assessments":266,"translations":269,"quiz":271},"01a0042a-852e-7338-83ed-a1f581d6c501","Stop Writing Media Queries for Font Size","A design system audit found five breakpoints for one heading. clamp() replaces all of them with one line that never jumps.","\u002Fmedia\u002Fcovers\u002Fcss-clamp-fluid-typography.png","2026-08-19T06:51:54.290Z",66,{"id":113,"name":114,"username":115,"avatarUrl":52,"headline":116},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":119},"CSS",[121,122,123,124],{"slug":74,"name":75,"color":52},{"slug":62,"name":63,"color":52},{"slug":66,"name":67,"color":52},{"slug":125,"name":126,"color":52},"accessibility","Accessibility",{"assessments":128},1,{"slug":46,"title":130},"clamp() fluid type — interactive playground",{"blocks":132,"version":128},[133,137,142,145,148,153,156,160,163,166,170,173,177,180,183,186,189,192,195,198,201,205,208,212,215,218,221,224,227,230,233,236,239,242,245,248,251],{"id":134,"html":135,"type":136},"b1","\u003Cp>A teammate opened a PR titled &quot;fix hero heading on small screens.&quot; The diff added a media query. Mine, reviewing it, found four more already in that file — one per breakpoint, added over eighteen months by four different people, each one patching the width the last person didn&#39;t think of:\u003C\u002Fp>","paragraph",{"id":138,"code":139,"type":140,"language":74,"highlight":141},"b2",".hero-heading {\n  font-size: 3rem;\n}\n\n@media (max-width: 1200px) {\n  .hero-heading { font-size: 2.5rem; }\n}\n\n@media (max-width: 992px) {\n  .hero-heading { font-size: 2.25rem; }\n}\n\n@media (max-width: 768px) {\n  .hero-heading { font-size: 1.75rem; }\n}\n\n@media (max-width: 480px) {\n  .hero-heading { font-size: 1.5rem; }\n}","code",[],{"id":143,"html":144,"type":136},"b3","\u003Cp>Five rules to make one number — the font size of one heading — track the width of the screen it&#39;s on. And it still didn&#39;t work everywhere: resize the window to 850px and the heading is stuck at the 992px value, a little too big for the space it actually has. Every gap between breakpoints is a size nobody chose, it&#39;s just whatever the nearest rule left behind.\u003C\u002Fp>",{"id":146,"html":147,"type":136},"b4","\u003Cp>Here&#39;s the part that stings: none of this has been necessary since 2020.\u003C\u002Fp>",{"id":149,"html":150,"text":151,"type":152,"level":31},"b5","The fix that isn&#39;t a breakpoint at all","The fix that isn't a breakpoint at all","heading",{"id":154,"html":155,"type":136},"b6","\u003Cp>\u003Ccode>clamp()\u003C\u002Fcode> takes three values — a minimum, a preferred value, and a maximum — and returns whichever one the situation calls for:\u003C\u002Fp>",{"id":157,"code":158,"type":140,"language":74,"highlight":159},"b7",".hero-heading {\n  font-size: clamp(1.5rem, 1rem + 2vw, 3rem);\n}",[],{"id":161,"html":162,"type":136},"b8","\u003Cp>Read it as a sentence: \u003Cem>never smaller than 1.5rem, never bigger than 3rem, and in between, scale with the viewport.\u003C\u002Fem> The five media queries above collapse into that one line — and unlike them, it doesn&#39;t have gaps. \u003Ccode>clamp()\u003C\u002Fcode> recalculates the size continuously, every pixel the viewport moves, so there&#39;s no &quot;850px value&quot; that got left behind. It&#39;s a formula, not a lookup table.\u003C\u002Fp>",{"id":164,"html":165,"type":136},"b9","\u003Cp>The middle value is where the &quot;preferred&quot; size lives, and it&#39;s \u003Ccode>1rem + 2vw\u003C\u002Fcode> — a fixed part plus a viewport-relative part — not just \u003Ccode>4vw\u003C\u002Fcode> on its own. That&#39;s not decoration. It&#39;s the one part of this pattern worth getting right, because the shortcut version quietly breaks something.\u003C\u002Fp>",{"id":167,"html":168,"text":169,"type":152,"level":31},"b10","The version that looks fine and isn&#39;t","The version that looks fine and isn't",{"id":171,"html":172,"type":136},"b11","\u003Cp>The formula you&#39;ll see in half the blog posts about this is:\u003C\u002Fp>",{"id":174,"code":175,"type":140,"language":74,"highlight":176},"b12","font-size: clamp(1.5rem, 4vw, 3rem);",[],{"id":178,"html":179,"type":136},"b13","\u003Cp>Simpler, and it scales exactly the same way visually. Try it in the playground below and you won&#39;t see a difference — until you go into your browser&#39;s settings and bump the default font size up, the way a low-vision reader routinely does. The \u003Ccode>4vw\u003C\u002Fcode>-only heading doesn&#39;t move. Not a little — not at all.\u003C\u002Fp>",{"id":181,"html":182,"type":136},"b14","\u003Cp>\u003Ccode>vw\u003C\u002Fcode> is a percentage of the viewport&#39;s width. It has no relationship to the page&#39;s root font size, so a user&#39;s default-font-size preference — a real accessibility setting, not an edge case — has nothing to attach to. \u003Ccode>rem\u003C\u002Fcode> does have that relationship: it&#39;s relative to the root element&#39;s font size, which is exactly what that setting changes. Mix a \u003Ccode>rem\u003C\u002Fcode> term into the preferred value and the heading grows when the user&#39;s preference does. Drop the \u003Ccode>rem\u003C\u002Fcode> term and you&#39;ve built a heading that&#39;s fluid for you, in your browser, at your settings, and static for the one person turning a knob specifically to make it bigger.\u003C\u002Fp>",{"id":184,"html":185,"type":136},"b15","\u003Cp>The fix costs nothing: \u003Ccode>1rem + 2vw\u003C\u002Fcode> instead of \u003Ccode>4vw\u003C\u002Fcode>. Same visual curve, but now it answers to two inputs instead of one.\u003C\u002Fp>",{"id":187,"html":188,"type":136},"b16","\u003C!-- playground:start -->",{"id":190,"html":191,"text":191,"type":152,"level":31},"b17","🎮 Try it yourself",{"id":193,"html":194,"type":136},"b18","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-clamp-fluid-typography\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":196,"html":197,"type":136},"b19","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":199,"html":200,"type":136},"b20","\u003C!-- playground:end -->",{"id":202,"html":203,"text":204,"type":152,"level":31},"b21","It&#39;s not just font-size","It's not just font-size",{"id":206,"html":207,"type":136},"b22","\u003Cp>Once \u003Ccode>clamp()\u003C\u002Fcode> clicks for type, the same three-value shape solves the same problem everywhere else a design system hardcodes a size per breakpoint: section padding, a hero&#39;s \u003Ccode>min-height\u003C\u002Fcode>, a sidebar&#39;s \u003Ccode>width\u003C\u002Fcode>, the \u003Ccode>max-width\u003C\u002Fcode> that controls line length in a paragraph. Anywhere the honest answer to &quot;what size should this be&quot; is &quot;it depends on the viewport, within limits&quot; — that&#39;s a \u003Ccode>clamp()\u003C\u002Fcode>, not a set of \u003Ccode>@media\u003C\u002Fcode> rules:\u003C\u002Fp>",{"id":209,"code":210,"type":140,"language":74,"highlight":211},"b23",".container {\n  padding-inline: clamp(1rem, 5vw, 4rem);\n}\n\n.article {\n  max-width: clamp(20rem, 60ch, 65rem);\n}",[],{"id":213,"html":214,"type":136},"b24","\u003Cp>The \u003Ccode>article\u003C\u002Fcode> line is worth a second look: mixing \u003Ccode>ch\u003C\u002Fcode> (a character-width unit) into the preferred value gives you a paragraph that keeps a readable line length as it grows, instead of one that just gets wider forever on an ultrawide monitor.\u003C\u002Fp>",{"id":216,"html":217,"text":217,"type":152,"level":31},"b25","Where media queries still win",{"id":219,"html":220,"type":136},"b26","\u003Cp>\u003Ccode>clamp()\u003C\u002Fcode> interpolates a \u003Cem>value\u003C\u002Fem>. It can&#39;t change layout — it won&#39;t swap a two-column grid to one column, hide a sidebar, or reorder flex items, because those are structural decisions, not points on a number line. If the real requirement is &quot;this should look and behave differently past a width,&quot; that&#39;s still a media query&#39;s job, and no amount of \u003Ccode>clamp()\u003C\u002Fcode> cleverness replaces it. The two aren&#39;t competing; \u003Ccode>clamp()\u003C\u002Fcode> just took over the part of the job — continuous scaling — that media queries were only ever approximating with a staircase.\u003C\u002Fp>",{"id":222,"html":223,"text":223,"type":152,"level":31},"b27","The one-line version",{"id":225,"html":226,"type":136},"b28","\u003Cp>Five media queries, four contributors, and gaps between every breakpoint were solving a problem \u003Ccode>clamp(MIN, PREFERRED, MAX)\u003C\u002Fcode> has handled in one line since 2020 — as long as the preferred value mixes a \u003Ccode>rem\u003C\u002Fcode> with the \u003Ccode>vw\u003C\u002Fcode>, so it still answers to the one setting that matters most.\u003C\u002Fp>",{"id":228,"html":229,"type":136},"b29","\u003Cp>Open your own stylesheet and search for \u003Ccode>@media\u003C\u002Fcode> rules that only ever change a \u003Ccode>font-size\u003C\u002Fcode> or a \u003Ccode>padding\u003C\u002Fcode>. How many of them are a \u003Ccode>clamp()\u003C\u002Fcode> waiting to happen?\u003C\u002Fp>",{"id":231,"html":232,"type":136},"b30","\u003C!-- quiz:start -->",{"id":234,"html":235,"text":235,"type":152,"level":31},"b31","🧠 Test yourself",{"id":237,"html":238,"type":136},"b32","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-clamp-fluid-typography\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":240,"html":241,"type":136},"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":136},"b34","\u003C!-- quiz:end -->",{"id":246,"type":247},"b35","divider",{"id":249,"html":250,"type":136},"b36","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":252,"type":253,"items":254,"ordered":18},"b37","list",[255,256,257,258,259],"⭐ \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>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","A teammate opened a PR titled \"fix hero heading on small screens.\" The diff added a media query. Mine, reviewing it, found four more already in that file — one per breakpoint, added over eighteen months by four different people, each one patching the width the last person didn't think of:\n\n```css\n.hero-heading {\n  font-size: 3rem;\n}\n\n@media (max-width: 1200px) {\n  .hero-heading { font-size: 2.5rem; }\n}\n\n@media (max-width: 992px) {\n  .hero-heading { font-size: 2.25rem; }\n}\n\n@media (max-width: 768px) {\n  .hero-heading { font-size: 1.75rem; }\n}\n\n@media (max-width: 480px) {\n  .hero-heading { font-size: 1.5rem; }\n}\n```\n\nFive rules to make one number — the font size of one heading — track the width of the screen it's on. And it still didn't work everywhere: resize the window to 850px and the heading is stuck at the 992px value, a little too big for the space it actually has. Every gap between breakpoints is a size nobody chose, it's just whatever the nearest rule left behind.\n\nHere's the part that stings: none of this has been necessary since 2020.\n\n## The fix that isn't a breakpoint at all\n\n`clamp()` takes three values — a minimum, a preferred value, and a maximum — and returns whichever one the situation calls for:\n\n```css\n.hero-heading {\n  font-size: clamp(1.5rem, 1rem + 2vw, 3rem);\n}\n```\n\nRead it as a sentence: *never smaller than 1.5rem, never bigger than 3rem, and in between, scale with the viewport.* The five media queries above collapse into that one line — and unlike them, it doesn't have gaps. `clamp()` recalculates the size continuously, every pixel the viewport moves, so there's no \"850px value\" that got left behind. It's a formula, not a lookup table.\n\nThe middle value is where the \"preferred\" size lives, and it's `1rem + 2vw` — a fixed part plus a viewport-relative part — not just `4vw` on its own. That's not decoration. It's the one part of this pattern worth getting right, because the shortcut version quietly breaks something.\n\n## The version that looks fine and isn't\n\nThe formula you'll see in half the blog posts about this is:\n\n```css\nfont-size: clamp(1.5rem, 4vw, 3rem);\n```\n\nSimpler, and it scales exactly the same way visually. Try it in the playground below and you won't see a difference — until you go into your browser's settings and bump the default font size up, the way a low-vision reader routinely does. The `4vw`-only heading doesn't move. Not a little — not at all.\n\n`vw` is a percentage of the viewport's width. It has no relationship to the page's root font size, so a user's default-font-size preference — a real accessibility setting, not an edge case — has nothing to attach to. `rem` does have that relationship: it's relative to the root element's font size, which is exactly what that setting changes. Mix a `rem` term into the preferred value and the heading grows when the user's preference does. Drop the `rem` term and you've built a heading that's fluid for you, in your browser, at your settings, and static for the one person turning a knob specifically to make it bigger.\n\nThe fix costs nothing: `1rem + 2vw` instead of `4vw`. Same visual curve, but now it answers to two inputs instead of one.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-clamp-fluid-typography\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## It's not just font-size\n\nOnce `clamp()` clicks for type, the same three-value shape solves the same problem everywhere else a design system hardcodes a size per breakpoint: section padding, a hero's `min-height`, a sidebar's `width`, the `max-width` that controls line length in a paragraph. Anywhere the honest answer to \"what size should this be\" is \"it depends on the viewport, within limits\" — that's a `clamp()`, not a set of `@media` rules:\n\n```css\n.container {\n  padding-inline: clamp(1rem, 5vw, 4rem);\n}\n\n.article {\n  max-width: clamp(20rem, 60ch, 65rem);\n}\n```\n\nThe `article` line is worth a second look: mixing `ch` (a character-width unit) into the preferred value gives you a paragraph that keeps a readable line length as it grows, instead of one that just gets wider forever on an ultrawide monitor.\n\n## Where media queries still win\n\n`clamp()` interpolates a *value*. It can't change layout — it won't swap a two-column grid to one column, hide a sidebar, or reorder flex items, because those are structural decisions, not points on a number line. If the real requirement is \"this should look and behave differently past a width,\" that's still a media query's job, and no amount of `clamp()` cleverness replaces it. The two aren't competing; `clamp()` just took over the part of the job — continuous scaling — that media queries were only ever approximating with a staircase.\n\n## The one-line version\n\nFive media queries, four contributors, and gaps between every breakpoint were solving a problem `clamp(MIN, PREFERRED, MAX)` has handled in one line since 2020 — as long as the preferred value mixes a `rem` with the `vw`, so it still answers to the one setting that matters most.\n\nOpen your own stylesheet and search for `@media` rules that only ever change a `font-size` or a `padding`. How many of them are a `clamp()` waiting to happen?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-clamp-fluid-typography\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---\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)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":107,"canonical":262,"description":108},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-clamp-fluid-typography","01a0042a-852f-729c-a65b-250c5e3a3aff",{"id":265,"locked":18},"01a0042a-8583-77a1-8526-37819a33c542",[267],{"id":45,"slug":46,"title":48,"_count":268},{"questions":51},[270],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":272,"questionCount":51},{"questions":51}]