[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-css-text-box-trim-headline-gap":44,"search-suggestions":243,"related-css-text-box-trim-headline-gap":284,"code:css:true:qibn7g":410,"code:css:true:zj0h00":411,"code:css:true:1u2po5w":412,"comments-01a0ec73-1ead-73da-8d85-b6b797fafb03":413},[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,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":75,"playground":77,"body":79,"bodyMd":226,"seo":227,"translationGroupId":230,"series":48,"podcastUrl":48,"verticalId":5,"thread":231,"assessments":233,"translations":239,"quiz":241},"01a0ec73-1ead-73da-8d85-b6b797fafb03","css-text-box-trim-headline-gap","Stop Fixing Headline Gaps With Negative Margins. Use `text-box-trim`",null,"Line-height reserves space for accent marks your headline never uses — a hand-tuned negative margin only fixes it for one font at one size. text-box-trim does, natively, from the font's own metrics.","\u002Fmedia\u002Fcovers\u002Fcss-text-box-trim-headline-gap.png",5,"2026-10-02T06:10:31.028Z",32,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"css","CSS",[64,66,69,72],{"slug":61,"name":65,"color":48},"Css",{"slug":67,"name":68,"color":48},"webdev","Webdev",{"slug":70,"name":71,"color":48},"frontend","Frontend",{"slug":73,"name":74,"color":48},"typography","Typography",{"assessments":76},1,{"slug":46,"title":78},"text-box-trim — the headline gap playground",{"blocks":80,"version":76},[81,85,88,91,96,99,104,107,110,114,117,120,123,127,131,134,138,141,144,147,150,153,156,159,162,165,168,171,174,177,180,183,186,189,192,195,198,201,208,211,214,217,220],{"id":82,"html":83,"type":84},"b1","\u003Cp>The Figma spec says 24px between the nav and the headline. You build it, open devtools, and there&#39;s clearly more than 24px of air sitting above the letters. You didn&#39;t misread the spec. The browser is reserving space your design tool never shows you.\u003C\u002Fp>","paragraph",{"id":86,"html":87,"type":84},"b2","\u003Cp>So you do what everyone does: nudge the heading up with a negative margin until the ruler in devtools finally reads 24px. It works — for this font, at this size, in this browser. Change the font, bump the line-height for mobile, or open it in Safari, and the gap is back, just a slightly different size than before.\u003C\u002Fp>",{"id":89,"html":90,"type":84},"b3","\u003Cp>You&#39;ve been fighting your font&#39;s ascent metrics and your line-height&#39;s half-leading. CSS just shipped a property that ends the fight without a single magic number.\u003C\u002Fp>",{"id":92,"html":93,"text":94,"type":95,"level":31},"b4","The hack, and why it&#39;s cursed","The hack, and why it's cursed","heading",{"id":97,"html":98,"type":84},"b5","\u003Cp>Here&#39;s the fix most people reach for first:\u003C\u002Fp>",{"id":100,"code":101,"type":102,"language":61,"highlight":103},"b6",".headline {\n  font-size: 4rem;\n  line-height: 1.1;\n  margin-top: -0.18em; \u002F* \"close enough\" nudge *\u002F\n}","code",[],{"id":105,"html":106,"type":84},"b7","\u003Cp>That \u003Ccode>-0.18em\u003C\u002Fcode> came from eyeballing devtools until the gap looked right. It&#39;s not derived from anything — it&#39;s a guess tuned to one font, one weight, one line-height. Swap the font from Inter to Georgia and the ratio of reserved space to cap-height changes, because different typefaces ship wildly different internal metrics. Your carefully tuned \u003Ccode>-0.18em\u003C\u002Fcode> now either under- or overshoots, and you&#39;re back in devtools with a ruler.\u003C\u002Fp>",{"id":108,"html":109,"type":84},"b8","\u003Cp>This got common enough that an entire library exists just to compute the right number for you: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fseek-oss\u002Fcapsize\">Capsize\u003C\u002Fa>, built at SEEK, reads a font&#39;s actual metrics (ascent, descent, cap height, unitsPerEm — the numbers baked into the font file) and generates the precise negative margins to trim it. It&#39;s clever, it works, and it&#39;s also a small build step and a metrics lookup table for a problem that shouldn&#39;t need either.\u003C\u002Fp>",{"id":111,"html":112,"text":113,"type":95,"level":31},"b9","What&#39;s actually eating the space","What's actually eating the space",{"id":115,"html":116,"type":84},"b10","\u003Cp>\u003Ccode>line-height\u003C\u002Fcode> doesn&#39;t measure your capital letters and add padding around them. It works from the font&#39;s own ascent and descent metrics — numbers baked in by whoever designed the typeface, sized to leave room for the tallest accented character the font ever needs to render, like \u003Ccode>Å\u003C\u002Fcode> or \u003Ccode>ĝ\u003C\u002Fcode>. Every line box gets that full reserved height whether or not a single accented character appears anywhere on your page — and \u003Ccode>line-height\u003C\u002Fcode> then adds half its extra spacing (the &quot;half-leading&quot;) above it and half below.\u003C\u002Fp>",{"id":118,"html":119,"type":84},"b11","\u003Cp>Crank \u003Ccode>line-height\u003C\u002Fcode> up for readability — which you should, body copy wants 1.5 to 1.6 — and you&#39;re not just adding line spacing. You&#39;re adding half-leading on top of that reserved-metrics gap, which is invisible in a paragraph and glaring on a 64px headline that&#39;s supposed to sit flush against a nav bar.\u003C\u002Fp>",{"id":121,"html":122,"type":84},"b12","\u003Cp>\u003Ccode>line-height: 1\u003C\u002Fcode> doesn&#39;t fix it either. It makes every line box exactly 1em tall — usually a bit less than the font&#39;s ascent + descent — but the glyphs are still positioned by those metrics, so the room for that \u003Ccode>Å\u003C\u002Fcode> is still baked in above the caps. You can watch this yourself: set a heading to \u003Ccode>line-height: 1\u003C\u002Fcode> and put a border around it. There&#39;s still air above the cap-height, because \u003Ccode>1\u003C\u002Fcode> isn&#39;t &quot;no leading,&quot; it&#39;s &quot;exactly 1em per line&quot; — and the font&#39;s ascent still sits well above its cap-height inside that 1em.\u003C\u002Fp>",{"id":124,"html":125,"text":126,"type":95,"level":31},"b13","The property: \u003Ccode>text-box-trim\u003C\u002Fcode>","The property: text-box-trim",{"id":128,"code":129,"type":102,"language":61,"highlight":130},"b14",".headline {\n  font-size: 4rem;\n  line-height: 1.1;\n  text-box-trim: trim-both;\n  text-box-edge: cap alphabetic;\n}",[],{"id":132,"html":133,"type":84},"b15","\u003Cp>Or the shorthand, one line:\u003C\u002Fp>",{"id":135,"code":136,"type":102,"language":61,"highlight":137},"b16",".headline {\n  text-box: trim-both cap alphabetic;\n}",[],{"id":139,"html":140,"type":84},"b17","\u003Cp>\u003Ccode>text-box-trim\u003C\u002Fcode> tells the browser which edge to cut: \u003Ccode>trim-start\u003C\u002Fcode> trims the top of the first line, \u003Ccode>trim-end\u003C\u002Fcode> the bottom of the last line, \u003Ccode>trim-both\u003C\u002Fcode> both. \u003Ccode>text-box-edge\u003C\u002Fcode> tells it \u003Cem>what to trim to\u003C\u002Fem> — \u003Ccode>cap alphabetic\u003C\u002Fcode> means &quot;cut the top down to the cap-height, cut the bottom down to the alphabetic baseline,&quot; which is exactly the crop a designer means when they draw a box tight around a headline in Figma. Swap it for \u003Ccode>ex alphabetic\u003C\u002Fcode> and it trims to the x-height instead of the cap-height — useful for lowercase-heavy display text.\u003C\u002Fp>",{"id":142,"html":143,"type":84},"b18","\u003Cp>No font metrics to look up. No build step. No number that quietly goes wrong the day someone changes the typeface. The browser already knows its own font&#39;s metrics — this property just asks it to use them.\u003C\u002Fp>",{"id":145,"html":146,"type":84},"b19","\u003Cp>Toggle it live below: same headline, same \u003Ccode>line-height\u003C\u002Fcode>, one checkbox. Watch the box outline snap tight to the letters, and then drag the line-height slider and see the old negative-margin hack fall apart while \u003Ccode>text-box-trim\u003C\u002Fcode> keeps up on its own.\u003C\u002Fp>",{"id":148,"html":149,"type":84},"b20","\u003C!-- playground:start -->",{"id":151,"html":152,"text":152,"type":95,"level":31},"b21","🎮 Try it yourself",{"id":154,"html":155,"type":84},"b22","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-text-box-trim-headline-gap\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":157,"html":158,"type":84},"b23","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":160,"html":161,"type":84},"b24","\u003C!-- playground:end -->",{"id":163,"html":164,"text":164,"type":95,"level":31},"b25","The one thing to check before you ship it",{"id":166,"html":167,"type":84},"b26","\u003Cp>This is genuinely new. Safari shipped it first (18.2, December 2024), Chrome followed in 133 (February 2025), and Firefox caught up in 154 (August 2026) — so the latest version of every major engine has it, but anyone on an older browser won&#39;t. Check your support targets before you rely on it for anything load-bearing.\u003C\u002Fp>",{"id":169,"html":170,"type":84},"b27","\u003Cp>The good news is the failure mode is boring, not broken: on a browser that doesn&#39;t understand \u003Ccode>text-box-trim\u003C\u002Fcode>, the declaration is simply ignored and you get your normal \u003Ccode>line-height\u003C\u002Fcode> box back — the exact spacing you already ship today. Nothing shifts, nothing overlaps, no layout breaks. That makes it a safe one to add now for the browsers that support it, with zero fallback code, while everyone else quietly keeps today&#39;s behavior until their browser catches up.\u003C\u002Fp>",{"id":172,"html":173,"text":173,"type":95,"level":31},"b28","Stop guessing the number",{"id":175,"html":176,"type":84},"b29","\u003Cp>The gap above your headline was never a margin bug. It was line-height reserving space for accent marks your text was never going to use, and for years the only fix was measuring a font&#39;s metrics by hand or pulling in a library that does it for you. \u003Ccode>text-box-trim\u003C\u002Fcode> and \u003Ccode>text-box-edge\u003C\u002Fcode> let the browser do what it already knows how to do — read its own font metrics — instead of you reverse-engineering them one \u003Ccode>-0.18em\u003C\u002Fcode> guess at a time.\u003C\u002Fp>",{"id":178,"html":179,"type":84},"b30","\u003Cp>Go check your own design system&#39;s headline component for a negative margin with a comment like \u003Ccode>\u002F* fixes gap above heading *\u002F\u003C\u002Fcode>. I&#39;d bet you&#39;ve got at least one. What&#39;s the ugliest one you&#39;ve shipped — and are you brave enough to paste the exact number in the comments?\u003C\u002Fp>",{"id":181,"html":182,"type":84},"b31","\u003C!-- quiz:start -->",{"id":184,"html":185,"text":185,"type":95,"level":31},"b32","🧠 Test yourself",{"id":187,"html":188,"type":84},"b33","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-text-box-trim-headline-gap\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":190,"html":191,"type":84},"b34","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":193,"html":194,"type":84},"b35","\u003C!-- quiz:end -->",{"id":196,"html":197,"type":84},"b36","\u003C!-- related:start -->",{"id":199,"html":200,"text":200,"type":95,"level":31},"b37","📚 Read next",{"id":202,"type":203,"items":204,"ordered":18},"b38","list",[205,206,207],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ftext-wrap-balance-headline-orphans\">You hand-edit headlines to avoid orphaned words. \u003Ccode>text-wrap: balance\u003C\u002Fcode> does it natively.\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-clamp-fluid-typography\">Stop Writing Media Queries for Font Size\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-custom-highlight-api-no-dom-mutation\">The Search Highlight That Deletes Your Selection\u003C\u002Fa>",{"id":209,"html":210,"type":84},"b39","\u003C!-- related:end -->",{"id":212,"type":213},"b40","divider",{"id":215,"html":216,"type":84},"b41","\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":218,"html":219,"type":84},"b42","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":221,"type":203,"items":222,"ordered":18},"b43",[223,224,225],"⭐ \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>","The Figma spec says 24px between the nav and the headline. You build it, open devtools, and there's clearly more than 24px of air sitting above the letters. You didn't misread the spec. The browser is reserving space your design tool never shows you.\n\nSo you do what everyone does: nudge the heading up with a negative margin until the ruler in devtools finally reads 24px. It works — for this font, at this size, in this browser. Change the font, bump the line-height for mobile, or open it in Safari, and the gap is back, just a slightly different size than before.\n\nYou've been fighting your font's ascent metrics and your line-height's half-leading. CSS just shipped a property that ends the fight without a single magic number.\n\n## The hack, and why it's cursed\n\nHere's the fix most people reach for first:\n\n```css\n.headline {\n  font-size: 4rem;\n  line-height: 1.1;\n  margin-top: -0.18em; \u002F* \"close enough\" nudge *\u002F\n}\n```\n\nThat `-0.18em` came from eyeballing devtools until the gap looked right. It's not derived from anything — it's a guess tuned to one font, one weight, one line-height. Swap the font from Inter to Georgia and the ratio of reserved space to cap-height changes, because different typefaces ship wildly different internal metrics. Your carefully tuned `-0.18em` now either under- or overshoots, and you're back in devtools with a ruler.\n\nThis got common enough that an entire library exists just to compute the right number for you: [Capsize](https:\u002F\u002Fgithub.com\u002Fseek-oss\u002Fcapsize), built at SEEK, reads a font's actual metrics (ascent, descent, cap height, unitsPerEm — the numbers baked into the font file) and generates the precise negative margins to trim it. It's clever, it works, and it's also a small build step and a metrics lookup table for a problem that shouldn't need either.\n\n## What's actually eating the space\n\n`line-height` doesn't measure your capital letters and add padding around them. It works from the font's own ascent and descent metrics — numbers baked in by whoever designed the typeface, sized to leave room for the tallest accented character the font ever needs to render, like `Å` or `ĝ`. Every line box gets that full reserved height whether or not a single accented character appears anywhere on your page — and `line-height` then adds half its extra spacing (the \"half-leading\") above it and half below.\n\nCrank `line-height` up for readability — which you should, body copy wants 1.5 to 1.6 — and you're not just adding line spacing. You're adding half-leading on top of that reserved-metrics gap, which is invisible in a paragraph and glaring on a 64px headline that's supposed to sit flush against a nav bar.\n\n`line-height: 1` doesn't fix it either. It makes every line box exactly 1em tall — usually a bit less than the font's ascent + descent — but the glyphs are still positioned by those metrics, so the room for that `Å` is still baked in above the caps. You can watch this yourself: set a heading to `line-height: 1` and put a border around it. There's still air above the cap-height, because `1` isn't \"no leading,\" it's \"exactly 1em per line\" — and the font's ascent still sits well above its cap-height inside that 1em.\n\n## The property: `text-box-trim`\n\n```css\n.headline {\n  font-size: 4rem;\n  line-height: 1.1;\n  text-box-trim: trim-both;\n  text-box-edge: cap alphabetic;\n}\n```\n\nOr the shorthand, one line:\n\n```css\n.headline {\n  text-box: trim-both cap alphabetic;\n}\n```\n\n`text-box-trim` tells the browser which edge to cut: `trim-start` trims the top of the first line, `trim-end` the bottom of the last line, `trim-both` both. `text-box-edge` tells it *what to trim to* — `cap alphabetic` means \"cut the top down to the cap-height, cut the bottom down to the alphabetic baseline,\" which is exactly the crop a designer means when they draw a box tight around a headline in Figma. Swap it for `ex alphabetic` and it trims to the x-height instead of the cap-height — useful for lowercase-heavy display text.\n\nNo font metrics to look up. No build step. No number that quietly goes wrong the day someone changes the typeface. The browser already knows its own font's metrics — this property just asks it to use them.\n\nToggle it live below: same headline, same `line-height`, one checkbox. Watch the box outline snap tight to the letters, and then drag the line-height slider and see the old negative-margin hack fall apart while `text-box-trim` keeps up on its own.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-text-box-trim-headline-gap\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 one thing to check before you ship it\n\nThis is genuinely new. Safari shipped it first (18.2, December 2024), Chrome followed in 133 (February 2025), and Firefox caught up in 154 (August 2026) — so the latest version of every major engine has it, but anyone on an older browser won't. Check your support targets before you rely on it for anything load-bearing.\n\nThe good news is the failure mode is boring, not broken: on a browser that doesn't understand `text-box-trim`, the declaration is simply ignored and you get your normal `line-height` box back — the exact spacing you already ship today. Nothing shifts, nothing overlaps, no layout breaks. That makes it a safe one to add now for the browsers that support it, with zero fallback code, while everyone else quietly keeps today's behavior until their browser catches up.\n\n## Stop guessing the number\n\nThe gap above your headline was never a margin bug. It was line-height reserving space for accent marks your text was never going to use, and for years the only fix was measuring a font's metrics by hand or pulling in a library that does it for you. `text-box-trim` and `text-box-edge` let the browser do what it already knows how to do — read its own font metrics — instead of you reverse-engineering them one `-0.18em` guess at a time.\n\nGo check your own design system's headline component for a negative margin with a comment like `\u002F* fixes gap above heading *\u002F`. I'd bet you've got at least one. What's the ugliest one you've shipped — and are you brave enough to paste the exact number in the comments?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-text-box-trim-headline-gap\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- [You hand-edit headlines to avoid orphaned words. `text-wrap: balance` does it natively.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ftext-wrap-balance-headline-orphans)\n- [Stop Writing Media Queries for Font Size](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-clamp-fluid-typography)\n- [The Search Highlight That Deletes Your Selection](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-custom-highlight-api-no-dom-mutation)\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":47,"canonical":228,"description":229},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-text-box-trim-headline-gap","Line-height reserves space for accent marks your headline never uses — a hand-tuned negative margin only fixes it for one font at one size. text-box-trim does, natively, from the f","01a0ec73-1eae-752b-a7b4-c936fab5517e",{"id":232,"locked":18},"01a0ec73-1fb2-766e-8026-c605f31525b2",[234],{"id":235,"slug":46,"title":236,"_count":237},"01a0ec73-1fd1-7593-b1d3-1127babc01f1","text-box-trim — trimming the headline gap",{"questions":238},7,[240],{"locale":13,"slug":46},{"id":235,"slug":46,"title":236,"_count":242,"questionCount":238},{"questions":238},[244,246,250,252,256,258,262,266,270,274,277,281],{"slug":67,"name":68,"articles":245},119,{"slug":247,"name":248,"articles":249},"javascript","Javascript",98,{"slug":70,"name":71,"articles":251},78,{"slug":253,"name":254,"articles":255},"tutorial","Tutorial",45,{"slug":61,"name":65,"articles":257},39,{"slug":259,"name":260,"articles":261},"typescript","Typescript",18,{"slug":263,"name":264,"articles":265},"performance","Performance",14,{"slug":267,"name":268,"articles":269},"react","React",13,{"slug":271,"name":272,"articles":273},"browser","Browser",11,{"slug":275,"name":276,"articles":273},"node","Node",{"slug":278,"name":279,"articles":280},"html","Html",8,{"slug":282,"name":283,"articles":238},"accessibility","Accessibility",{"items":285,"meta":409},[286,298,317,335,353,372,390],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":287,"likeCount":19,"commentCount":19,"author":288,"vertical":289,"topic":290,"tags":291,"_count":296,"playground":297,"hasQuiz":17,"hasPlayground":17},33,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[292,293,294,295],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"assessments":76},{"slug":46},{"id":299,"slug":300,"title":301,"subtitle":48,"excerpt":302,"coverUrl":303,"locale":13,"readingMinutes":304,"publishedAt":305,"viewCount":306,"likeCount":19,"commentCount":19,"author":307,"vertical":308,"topic":309,"tags":310,"_count":315,"playground":316,"hasQuiz":17,"hasPlayground":17},"01a0ec73-195e-713c-b27c-9beace8ef7df","nth-child-of-selector-list","`:nth-child(even)` Counts Hidden Rows. `of S` Fixes Your Stripes","nth-child(even) counts every element in the DOM, including the ones you just hid with display:none. The fix isn't JavaScript — it's a CSS selector most people have never had a reason to use.","\u002Fmedia\u002Fcovers\u002Fnth-child-of-selector-list.png",4,"2026-10-01T06:10:26.375Z",116,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[311,312,313,314],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":253,"name":254,"color":48},{"assessments":76},{"slug":300},{"id":318,"slug":319,"title":320,"subtitle":48,"excerpt":321,"coverUrl":322,"locale":13,"readingMinutes":238,"publishedAt":323,"viewCount":324,"likeCount":19,"commentCount":19,"author":325,"vertical":326,"topic":327,"tags":328,"_count":333,"playground":334,"hasQuiz":17,"hasPlayground":17},"01a0ec73-013a-70ca-bcf4-046f64b93003","css-backdrop-filter-frosted-glass","Your `backdrop-filter` Is Working. Your Background Is Hiding It.","backdrop-filter looks like a one-line copy from a Codepen: blur(20px), ship the frosted navbar. Half the time it renders as a plain flat panel instead — because backdrop-filter blurs whatever is BEHIND an element, and that only shows up if the element itself is see-through. Here's the transparency rule, the stacking-context trap it quietly sets, and why it's one of the priciest effects you can scroll past.","\u002Fmedia\u002Fcovers\u002Fcss-backdrop-filter-frosted-glass.png","2026-09-30T12:10:22.069Z",121,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[329,330,331,332],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":253,"name":254,"color":48},{"assessments":76},{"slug":319},{"id":336,"slug":337,"title":338,"subtitle":48,"excerpt":339,"coverUrl":340,"locale":13,"readingMinutes":51,"publishedAt":341,"viewCount":342,"likeCount":19,"commentCount":19,"author":343,"vertical":344,"topic":345,"tags":346,"_count":351,"playground":352,"hasQuiz":17,"hasPlayground":17},"01a0a9ea-93a3-7734-b359-6d7cc4df502b","customizable-select-appearance-base-select","The Dropdown You've Been Faking for a Decade","Every hand-built 'custom select' with icons and a styled popup exists because the real \u003Cselect> couldn't do that. appearance: base-select just closed the gap — without you losing the native picker's keyboard nav and mobile UI.","\u002Fmedia\u002Fcovers\u002Fcustomizable-select-appearance-base-select.png","2026-09-22T19:36:39.450Z",289,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[347,348,349,350],{"slug":61,"name":65,"color":48},{"slug":278,"name":279,"color":48},{"slug":67,"name":68,"color":48},{"slug":282,"name":283,"color":48},{"assessments":76},{"slug":337},{"id":354,"slug":355,"title":356,"subtitle":48,"excerpt":357,"coverUrl":358,"locale":13,"readingMinutes":359,"publishedAt":360,"viewCount":361,"likeCount":19,"commentCount":19,"author":362,"vertical":363,"topic":364,"tags":365,"_count":370,"playground":371,"hasQuiz":17,"hasPlayground":17},"01a0af18-24b4-73ad-95ed-c9479ae0e91f","css-dynamic-viewport-units-dvh-svh-lvh","Stop Writing JavaScript To Fix `100vh` On Mobile","Your full-screen hero looks perfect in the simulator and cuts off the CTA on a real phone. It's not a bug in your code — it's `vh` answering a question you didn't ask. Three new units fix it with zero JavaScript.","\u002Fmedia\u002Fcovers\u002Fcss-dynamic-viewport-units-dvh-svh-lvh.png",6,"2026-09-22T19:35:37.351Z",303,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[366,367,368,369],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":253,"name":254,"color":48},{"assessments":76},{"slug":355},{"id":373,"slug":374,"title":375,"subtitle":48,"excerpt":376,"coverUrl":377,"locale":13,"readingMinutes":304,"publishedAt":378,"viewCount":379,"likeCount":19,"commentCount":19,"author":380,"vertical":381,"topic":382,"tags":383,"_count":388,"playground":389,"hasQuiz":17,"hasPlayground":17},"01a09a84-4147-713b-b0e9-9ffc501e378b","css-color-scheme-native-dark-mode-form-controls","The CSS Property That Fixes Dark Mode's Ugly White Boxes","You shipped dark mode. The page is black, the text is white — and then the date picker opens like a floodlight. color-scheme is the one-line CSS property that tells the browser your native form controls need to catch up too.","\u002Fmedia\u002Fcovers\u002Fcss-color-scheme-native-dark-mode-form-controls.png","2026-09-18T17:22:32.837Z",281,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[384,385,386,387],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":282,"name":283,"color":48},{"assessments":76},{"slug":374},{"id":391,"slug":392,"title":393,"subtitle":48,"excerpt":394,"coverUrl":395,"locale":13,"readingMinutes":359,"publishedAt":396,"viewCount":397,"likeCount":19,"commentCount":19,"author":398,"vertical":399,"topic":400,"tags":402,"_count":407,"playground":408,"hasQuiz":17,"hasPlayground":17},"01a09525-10c5-714d-9021-2e375a15a5d3","matchmedia-watch-media-queries-from-js","Stop Debouncing Resize. The Browser Already Watches It.","A debounced resize handler comparing window.innerWidth to a magic number can't ever see dark mode or reduced motion — and it fires dozens of times for one thing matchMedia fires once for.","\u002Fmedia\u002Fcovers\u002Fmatchmedia-watch-media-queries-from-js.png","2026-09-17T11:19:53.454Z",439,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":247,"name":401},"JavaScript",[403,404,405,406],{"slug":247,"name":248,"color":48},{"slug":67,"name":68,"color":48},{"slug":61,"name":65,"color":48},{"slug":271,"name":272,"color":48},{"assessments":76},{"slug":392},{"page":76,"perPage":238,"total":257,"totalPages":359},"\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">.headline\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">font-size\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">4\u003C\u002Fspan>\u003Cspan class=\"shj-var\">rem\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">line-height\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">1.1\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">margin-top\u003C\u002Fspan>: -\u003Cspan class=\"shj-num\">0.18\u003C\u002Fspan>\u003Cspan class=\"shj-var\">em\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F* \"close enough\" nudge *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">.headline\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">font-size\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">4\u003C\u002Fspan>\u003Cspan class=\"shj-var\">rem\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">line-height\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">1.1\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">text-box-trim\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">trim-both\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">text-box-edge\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">cap\u003C\u002Fspan> \u003Cspan class=\"shj-num\">alphabetic\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">.headline\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">text-box\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">trim-both\u003C\u002Fspan> \u003Cspan class=\"shj-num\">cap\u003C\u002Fspan> \u003Cspan class=\"shj-num\">alphabetic\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":414},[]]