[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-calc-size-animate-to-auto":32,"search-suggestions":48,"quiz-article-calc-size-animate-to-auto":93},[4,20],{"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,"kind":35,"title":36,"description":37,"config":38,"verticalId":5,"vertical":43,"course":40,"_count":44,"access":45,"attempts":47,"questionCount":39},"019ff73a-edac-71de-b54b-125955a75432","calc-size-animate-to-auto","PRACTICE_QUIZ","calc-size() and interpolate-size — animating to height: auto","Check what actually stuck: why height: auto never animated, what the old workarounds cost you, and how calc-size() and interpolate-size close the gap for real.",{"questionCount":39,"timeLimitSec":40,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":41,"maxAttempts":40,"revealAnswers":42,"allowFlagging":18,"allowBacktracking":17},8,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":39},{"allowed":17,"reason":46},"FREE",[],[49,53,57,60,64,68,71,75,79,83,87,90],{"slug":50,"name":51,"articles":52},"webdev","Webdev",55,{"slug":54,"name":55,"articles":56},"frontend","Frontend",49,{"slug":58,"name":59,"articles":56},"javascript","Javascript",{"slug":61,"name":62,"articles":63},"css","Css",20,{"slug":65,"name":66,"articles":67},"typescript","Typescript",10,{"slug":69,"name":70,"articles":39},"performance","Performance",{"slug":72,"name":73,"articles":74},"grammar","Grammar",6,{"slug":76,"name":77,"articles":78},"react","React",5,{"slug":80,"name":81,"articles":82},"node","Node",4,{"slug":84,"name":85,"articles":86},"ai","Ai",3,{"slug":88,"name":89,"articles":86},"html","Html",{"slug":91,"name":92,"articles":86},"tutorial","Tutorial",{"id":94,"slug":34,"title":95,"subtitle":40,"excerpt":96,"coverUrl":97,"locale":13,"readingMinutes":74,"publishedAt":98,"viewCount":99,"likeCount":19,"commentCount":19,"author":100,"vertical":105,"topic":106,"tags":108,"_count":115,"playground":117,"body":119,"bodyMd":266,"seo":267,"translationGroupId":270,"thread":271,"assessments":273,"translations":276,"quiz":278},"019ff73a-ed77-732e-a13f-8be6d6939332","I Can Finally Delete My max-height: 9999px Animation Hack","CSS transitions have never been able to animate to height: auto, because auto isn't a number the engine can interpolate. calc-size() and interpolate-size finally give it real numbers — no JavaScript, no magic-number ceiling.","\u002Fmedia\u002Fcovers\u002Fcalc-size-animate-to-auto.png","2026-08-16T06:46:49.217Z",42,{"id":101,"name":102,"username":103,"avatarUrl":40,"headline":104},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":107},"CSS",[109,110,111,112],{"slug":61,"name":62,"color":40},{"slug":50,"name":51,"color":40},{"slug":54,"name":55,"color":40},{"slug":113,"name":114,"color":40},"animation","Animation",{"assessments":116},1,{"slug":34,"title":118},"height: auto animation — three techniques, live",{"blocks":120,"version":116},[121,125,128,132,135,140,143,146,151,154,157,160,163,166,170,173,177,180,183,186,189,192,195,198,202,205,208,212,215,218,221,224,227,230,233,236,239,242,245,248,251,254,257],{"id":122,"html":123,"type":124},"b1","\u003Cp>Open any accordion, dropdown, or &quot;show more&quot; panel you&#39;ve shipped and grep its CSS for \u003Ccode>max-height\u003C\u002Fcode>. You&#39;ll find a number: \u003Ccode>500px\u003C\u002Fcode>, \u003Ccode>800px\u003C\u002Fcode>, \u003Ccode>9999px\u003C\u002Fcode>. Ask whoever wrote it why that number, specifically, and watch them shrug. &quot;Felt safe.&quot;\u003C\u002Fp>","paragraph",{"id":126,"html":127,"type":124},"b2","\u003Cp>It isn&#39;t safe. It&#39;s a guess wearing a unit. And for over a decade that guess was the best CSS could do, because there&#39;s a rule you&#39;ve never been able to break: \u003Cstrong>you cannot transition to \u003Ccode>height: auto\u003C\u002Fcode>.\u003C\u002Fstrong> Not &quot;it&#39;s awkward&quot; — it genuinely does not work. That finally changed. Almost nobody&#39;s using it yet.\u003C\u002Fp>",{"id":129,"html":130,"text":130,"type":131,"level":31},"b3","The guess that always breaks eventually","heading",{"id":133,"html":134,"type":124},"b4","\u003Cp>Here&#39;s the fix you&#39;ve probably shipped, because it&#39;s the first thing anyone tries:\u003C\u002Fp>",{"id":136,"code":137,"type":138,"language":61,"highlight":139},"b5",".panel {\n  max-height: 0;\n  overflow: hidden;\n  transition: max-height 300ms ease;\n}\n\n.panel.open {\n  max-height: 500px; \u002F* a guess bigger than the content will ever be *\u002F\n}","code",[],{"id":141,"html":142,"type":124},"b6","\u003Cp>It works in the demo. It works in the PR review. Then three months later a support rep pastes a 40-line answer into the FAQ panel this was built for, the real content hits 640px, and the panel clips the last two lines with no scrollbar and no error — it just silently loses content. You bump the number to \u003Ccode>9999px\u003C\u002Fcode> to &quot;fix&quot; it for good, and now every short panel — the ones that are actually 80px tall — takes the same visible beat to finish opening, because the browser is animating from 0 to 9999 and just happens to arrive at the real content early. You can see the pause if you watch closely. Users don&#39;t watch closely; they just feel that something&#39;s slightly off.\u003C\u002Fp>",{"id":144,"html":145,"type":124},"b7","\u003Cp>So you reach for the other fix, the one that actually measures reality:\u003C\u002Fp>",{"id":147,"code":148,"type":138,"language":149,"highlight":150},"b8","function toggle(panel) {\n  const isOpen = panel.classList.contains('open');\n  panel.style.height = isOpen ? '0px' : panel.scrollHeight + 'px';\n  panel.classList.toggle('open');\n}","js",[],{"id":152,"html":153,"type":124},"b9","\u003Cp>This one&#39;s honest — it reads the real height instead of guessing. But now every toggle forces a layout read before the animation can even start, and if the content inside can change while the panel is open (an image loads, a comment thread grows), your cached \u003Ccode>scrollHeight\u003C\u002Fcode> goes stale and you need a \u003Ccode>ResizeObserver\u003C\u002Fcode> just to keep the number honest. You&#39;ve traded a wrong number for a right number that needs its own maintenance.\u003C\u002Fp>",{"id":155,"html":156,"type":124},"b10","\u003Cp>Quick check before you scroll: is there a \u003Ccode>max-height:\u003C\u002Fcode> with a suspicious round number in something you shipped this year? There usually is.\u003C\u002Fp>",{"id":158,"html":159,"text":159,"type":131,"level":31},"b11","Why this was never a skill issue",{"id":161,"html":162,"type":124},"b12","\u003Cp>Here&#39;s the part that&#39;s easy to miss: this isn&#39;t a case where you were holding CSS wrong. A CSS transition works by taking a start value and an end value and asking the engine to compute the in-between frames — 0px, 40px, 120px, 480px, and so on until it lands on the target. That only works if both ends are actual numbers.\u003C\u002Fp>",{"id":164,"html":165,"type":124},"b13","\u003Cp>\u003Ccode>auto\u003C\u002Fcode> was never a number. It&#39;s an instruction: &quot;run layout, and whatever height that produces, use it.&quot; The browser can&#39;t interpolate toward an instruction — it doesn&#39;t know the destination until layout has already run, and running layout \u003Cem>is\u003C\u002Fem> the thing the animation is supposed to be showing you getting closer to. So historically, the engine&#39;s only options were to snap directly to the computed height (no animation) or refuse to run the transition on that property at all. Every hack above exists to hand the engine a real number instead, because the engine genuinely couldn&#39;t make one up.\u003C\u002Fp>",{"id":167,"html":168,"text":169,"type":131,"level":31},"b14","The actual fix: \u003Ccode>interpolate-size\u003C\u002Fcode>","The actual fix: interpolate-size",{"id":171,"html":172,"type":124},"b15","\u003Cp>Chromium 129 shipped the piece that was missing: a way to tell the engine &quot;you&#39;re allowed to treat intrinsic keywords — \u003Ccode>auto\u003C\u002Fcode>, \u003Ccode>max-content\u003C\u002Fcode>, \u003Ccode>fit-content\u003C\u002Fcode> — as numbers you can animate toward,&quot; instead of refusing on principle.\u003C\u002Fp>",{"id":174,"code":175,"type":138,"language":61,"highlight":176},"b16",":root {\n  interpolate-size: allow-keywords;\n}\n\n.panel {\n  height: 0;\n  overflow: hidden;\n  transition: height 300ms ease;\n}\n\n.panel.open {\n  height: auto;\n}",[],{"id":178,"html":179,"type":124},"b17","\u003Cp>That&#39;s it. No \u003Ccode>scrollHeight\u003C\u002Fcode>, no guessed ceiling, no \u003Ccode>ResizeObserver\u003C\u002Fcode>. \u003Ccode>interpolate-size\u003C\u002Fcode> defaults to \u003Ccode>numeric-only\u003C\u002Fcode> — the old, safe behavior — so setting it to \u003Ccode>allow-keywords\u003C\u002Fcode> once on \u003Ccode>:root\u003C\u002Fcode> is what flips the switch for the whole page. The browser now resolves \u003Ccode>auto\u003C\u002Fcode>&#39;s real computed height \u003Cem>before\u003C\u002Fem> the transition starts, then genuinely animates toward it, the same way it would animate toward \u003Ccode>320px\u003C\u002Fcode>.\u003C\u002Fp>",{"id":181,"html":182,"type":124},"b18","\u003C!-- playground:start -->",{"id":184,"html":185,"text":185,"type":131,"level":31},"b19","🎮 Try it yourself",{"id":187,"html":188,"type":124},"b20","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcalc-size-animate-to-auto\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":190,"html":191,"type":124},"b21","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":193,"html":194,"type":124},"b22","\u003C!-- playground:end -->",{"id":196,"html":197,"type":124},"b23","\u003Cp>Need something more surgical than a flat toggle — a percentage of an intrinsic size, say, or a multiple of \u003Ccode>min-content\u003C\u002Fcode> for a custom easing step? That&#39;s what \u003Ccode>calc-size()\u003C\u002Fcode> is for: it lets you do math on an intrinsic keyword the way \u003Ccode>calc()\u003C\u002Fcode> does math on a length.\u003C\u002Fp>",{"id":199,"code":200,"type":138,"language":61,"highlight":201},"b24",".panel {\n  \u002F* 70% of the content's natural height, as an animation step *\u002F\n  height: calc-size(auto, size * 0.7);\n}",[],{"id":203,"html":204,"type":124},"b25","\u003Cp>\u003Ccode>size\u003C\u002Fcode> inside the function stands in for whatever the basis keyword (\u003Ccode>auto\u003C\u002Fcode>, \u003Ccode>min-content\u003C\u002Fcode>, \u003Ccode>max-content\u003C\u002Fcode>, \u003Ccode>fit-content\u003C\u002Fcode>, \u003Ccode>stretch\u003C\u002Fcode>, or \u003Ccode>contain\u003C\u002Fcode>) resolves to. Using \u003Ccode>calc-size()\u003C\u002Fcode> anywhere in a value automatically turns on \u003Ccode>interpolate-size: allow-keywords\u003C\u002Fcode> for that value, so most of the time the plain \u003Ccode>height: auto\u003C\u002Fcode> version above is all you need — reach for \u003Ccode>calc-size()\u003C\u002Fcode> only when you actually need to compute something on top of the keyword.\u003C\u002Fp>",{"id":206,"html":207,"type":124},"b26","\u003Cp>The spec authors also wired this into \u003Ccode>&lt;details&gt;\u003C\u002Fcode> directly, which is worth stealing verbatim:\u003C\u002Fp>",{"id":209,"code":210,"type":138,"language":61,"highlight":211},"b27",":root {\n  interpolate-size: allow-keywords;\n}\n\ndetails::details-content {\n  height: 0;\n  transition: height 500ms;\n}\n\ndetails[open]::details-content {\n  height: max-content;\n}",[],{"id":213,"html":214,"type":124},"b28","\u003Cp>A native disclosure widget, animating open and closed, with zero JavaScript and zero measured pixels.\u003C\u002Fp>",{"id":216,"html":217,"text":217,"type":131,"level":31},"b29","The catch, said plainly",{"id":219,"html":220,"type":124},"b30","\u003Cp>\u003Ccode>interpolate-size\u003C\u002Fcode> and \u003Ccode>calc-size()\u003C\u002Fcode> currently ship in Chrome and Edge 129+ (and their Chromium-based siblings). As of this writing, Firefox and Safari don&#39;t support either one — there are open requests to add them, but no shipped implementation yet.\u003C\u002Fp>",{"id":222,"html":223,"type":124},"b31","\u003Cp>The good news is how it fails. \u003Ccode>interpolate-size: allow-keywords\u003C\u002Fcode> is a genuinely safe rollout: a browser that doesn&#39;t understand it just keeps the default \u003Ccode>numeric-only\u003C\u002Fcode> behavior, which means the \u003Ccode>height: auto\u003C\u002Fcode> transition doesn&#39;t error or break — it just doesn&#39;t animate. The panel snaps open instantly, exactly like it would have before this property existed. Nothing clips, nothing crashes, and you don&#39;t need a JavaScript feature-detect to stay safe; wrap it in \u003Ccode>@supports (interpolate-size: allow-keywords) { … }\u003C\u002Fcode> only if you want to swap in a fallback transition for unsupported browsers, not because the unguarded version is dangerous.\u003C\u002Fp>",{"id":225,"html":226,"type":124},"b32","\u003Cp>That makes it a real progressive enhancement, not a bet: ship it today, and roughly a third of your users get a smooth open\u002Fclose for free while the rest get the exact snap behavior they already have.\u003C\u002Fp>",{"id":228,"html":229,"text":229,"type":131,"level":31},"b33","The takeaway",{"id":231,"html":232,"type":124},"b34","\u003Cp>The \u003Ccode>max-height: 9999px\u003C\u002Fcode> in your codebase isn&#39;t a bug you introduced — it&#39;s a scar from a real gap in CSS that&#39;s only now closing. You don&#39;t need to rip it out today; Firefox and Safari users still need it. But the next accordion, the next FAQ panel, the next &quot;show more&quot; you build gets one property on \u003Ccode>:root\u003C\u002Fcode> instead of a guessed ceiling or a \u003Ccode>ResizeObserver\u003C\u002Fcode>.\u003C\u002Fp>",{"id":234,"html":235,"type":124},"b35","\u003Cp>Go find your own \u003Ccode>max-height:\u003C\u002Fcode> guess and try swapping it for \u003Ccode>interpolate-size: allow-keywords\u003C\u002Fcode>. What number were you hiding behind — and did you ever find out the hard way that it was too small?\u003C\u002Fp>",{"id":237,"html":238,"type":124},"b36","\u003C!-- quiz:start -->",{"id":240,"html":241,"text":241,"type":131,"level":31},"b37","🧠 Test yourself",{"id":243,"html":244,"type":124},"b38","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcalc-size-animate-to-auto\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":246,"html":247,"type":124},"b39","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":249,"html":250,"type":124},"b40","\u003C!-- quiz:end -->",{"id":252,"type":253},"b41","divider",{"id":255,"html":256,"type":124},"b42","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":258,"type":259,"items":260,"ordered":18},"b43","list",[261,262,263,264,265],"⭐ \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>","Open any accordion, dropdown, or \"show more\" panel you've shipped and grep its CSS for `max-height`. You'll find a number: `500px`, `800px`, `9999px`. Ask whoever wrote it why that number, specifically, and watch them shrug. \"Felt safe.\"\n\nIt isn't safe. It's a guess wearing a unit. And for over a decade that guess was the best CSS could do, because there's a rule you've never been able to break: **you cannot transition to `height: auto`.** Not \"it's awkward\" — it genuinely does not work. That finally changed. Almost nobody's using it yet.\n\n## The guess that always breaks eventually\n\nHere's the fix you've probably shipped, because it's the first thing anyone tries:\n\n```css\n.panel {\n  max-height: 0;\n  overflow: hidden;\n  transition: max-height 300ms ease;\n}\n\n.panel.open {\n  max-height: 500px; \u002F* a guess bigger than the content will ever be *\u002F\n}\n```\n\nIt works in the demo. It works in the PR review. Then three months later a support rep pastes a 40-line answer into the FAQ panel this was built for, the real content hits 640px, and the panel clips the last two lines with no scrollbar and no error — it just silently loses content. You bump the number to `9999px` to \"fix\" it for good, and now every short panel — the ones that are actually 80px tall — takes the same visible beat to finish opening, because the browser is animating from 0 to 9999 and just happens to arrive at the real content early. You can see the pause if you watch closely. Users don't watch closely; they just feel that something's slightly off.\n\nSo you reach for the other fix, the one that actually measures reality:\n\n```js\nfunction toggle(panel) {\n  const isOpen = panel.classList.contains('open');\n  panel.style.height = isOpen ? '0px' : panel.scrollHeight + 'px';\n  panel.classList.toggle('open');\n}\n```\n\nThis one's honest — it reads the real height instead of guessing. But now every toggle forces a layout read before the animation can even start, and if the content inside can change while the panel is open (an image loads, a comment thread grows), your cached `scrollHeight` goes stale and you need a `ResizeObserver` just to keep the number honest. You've traded a wrong number for a right number that needs its own maintenance.\n\nQuick check before you scroll: is there a `max-height:` with a suspicious round number in something you shipped this year? There usually is.\n\n## Why this was never a skill issue\n\nHere's the part that's easy to miss: this isn't a case where you were holding CSS wrong. A CSS transition works by taking a start value and an end value and asking the engine to compute the in-between frames — 0px, 40px, 120px, 480px, and so on until it lands on the target. That only works if both ends are actual numbers.\n\n`auto` was never a number. It's an instruction: \"run layout, and whatever height that produces, use it.\" The browser can't interpolate toward an instruction — it doesn't know the destination until layout has already run, and running layout *is* the thing the animation is supposed to be showing you getting closer to. So historically, the engine's only options were to snap directly to the computed height (no animation) or refuse to run the transition on that property at all. Every hack above exists to hand the engine a real number instead, because the engine genuinely couldn't make one up.\n\n## The actual fix: `interpolate-size`\n\nChromium 129 shipped the piece that was missing: a way to tell the engine \"you're allowed to treat intrinsic keywords — `auto`, `max-content`, `fit-content` — as numbers you can animate toward,\" instead of refusing on principle.\n\n```css\n:root {\n  interpolate-size: allow-keywords;\n}\n\n.panel {\n  height: 0;\n  overflow: hidden;\n  transition: height 300ms ease;\n}\n\n.panel.open {\n  height: auto;\n}\n```\n\nThat's it. No `scrollHeight`, no guessed ceiling, no `ResizeObserver`. `interpolate-size` defaults to `numeric-only` — the old, safe behavior — so setting it to `allow-keywords` once on `:root` is what flips the switch for the whole page. The browser now resolves `auto`'s real computed height *before* the transition starts, then genuinely animates toward it, the same way it would animate toward `320px`.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcalc-size-animate-to-auto\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\nNeed something more surgical than a flat toggle — a percentage of an intrinsic size, say, or a multiple of `min-content` for a custom easing step? That's what `calc-size()` is for: it lets you do math on an intrinsic keyword the way `calc()` does math on a length.\n\n```css\n.panel {\n  \u002F* 70% of the content's natural height, as an animation step *\u002F\n  height: calc-size(auto, size * 0.7);\n}\n```\n\n`size` inside the function stands in for whatever the basis keyword (`auto`, `min-content`, `max-content`, `fit-content`, `stretch`, or `contain`) resolves to. Using `calc-size()` anywhere in a value automatically turns on `interpolate-size: allow-keywords` for that value, so most of the time the plain `height: auto` version above is all you need — reach for `calc-size()` only when you actually need to compute something on top of the keyword.\n\nThe spec authors also wired this into `\u003Cdetails>` directly, which is worth stealing verbatim:\n\n```css\n:root {\n  interpolate-size: allow-keywords;\n}\n\ndetails::details-content {\n  height: 0;\n  transition: height 500ms;\n}\n\ndetails[open]::details-content {\n  height: max-content;\n}\n```\n\nA native disclosure widget, animating open and closed, with zero JavaScript and zero measured pixels.\n\n## The catch, said plainly\n\n`interpolate-size` and `calc-size()` currently ship in Chrome and Edge 129+ (and their Chromium-based siblings). As of this writing, Firefox and Safari don't support either one — there are open requests to add them, but no shipped implementation yet.\n\nThe good news is how it fails. `interpolate-size: allow-keywords` is a genuinely safe rollout: a browser that doesn't understand it just keeps the default `numeric-only` behavior, which means the `height: auto` transition doesn't error or break — it just doesn't animate. The panel snaps open instantly, exactly like it would have before this property existed. Nothing clips, nothing crashes, and you don't need a JavaScript feature-detect to stay safe; wrap it in `@supports (interpolate-size: allow-keywords) { … }` only if you want to swap in a fallback transition for unsupported browsers, not because the unguarded version is dangerous.\n\nThat makes it a real progressive enhancement, not a bet: ship it today, and roughly a third of your users get a smooth open\u002Fclose for free while the rest get the exact snap behavior they already have.\n\n## The takeaway\n\nThe `max-height: 9999px` in your codebase isn't a bug you introduced — it's a scar from a real gap in CSS that's only now closing. You don't need to rip it out today; Firefox and Safari users still need it. But the next accordion, the next FAQ panel, the next \"show more\" you build gets one property on `:root` instead of a guessed ceiling or a `ResizeObserver`.\n\nGo find your own `max-height:` guess and try swapping it for `interpolate-size: allow-keywords`. What number were you hiding behind — and did you ever find out the hard way that it was too small?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcalc-size-animate-to-auto\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":95,"canonical":268,"description":269},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcalc-size-animate-to-auto","CSS transitions have never been able to animate to height: auto, because auto isn't a number the engine can interpolate. calc-size() and interpolate-size finally give it real numbe","019ff73a-ed77-732e-a13f-8d6cabd3406a",{"id":272,"locked":18},"019ff73a-ed94-735f-98cb-d20798a681b5",[274],{"id":33,"slug":34,"title":36,"_count":275},{"questions":39},[277],{"locale":13,"slug":34},{"id":33,"slug":34,"title":36,"_count":279,"questionCount":39},{"questions":39}]