CSS & Layout
The classic wall: you can't CSS-transition to height: auto. A one-line CSS Grid trick gets you a smooth open-and-close to the content's natural height — no measuring, no script.
Live Result
Click the header to toggle — the panel glides open and shut to fit its text, with no fixed height set anywhere.
<div class="accordion">
<input type="checkbox" id="acc" checked>
<label class="acc-head" for="acc">
What's included? <span class="chev">›</span>
</label>
<!-- This wrapper is what we animate -->
<div class="acc-body">
<div class="acc-inner">
<p>Strategy session, custom design, and a hand-built
front end. The panel below grows to fit whatever
content lives inside it — no fixed height, no
JavaScript measuring anything.</p>
</div>
</div>
</div> .acc-body {
display: grid;
/* Collapsed: the row has zero height... */
grid-template-rows: 0fr;
transition: grid-template-rows .35s ease;
}
/* ...open: the row expands to its content's natural height */
.accordion input:checked ~ .acc-body {
grid-template-rows: 1fr;
}
/* A grid item can't go below its content size unless its
overflow is hidden — this is the piece that makes 0fr work */
.acc-inner {
overflow: hidden;
} Browsers refuse to animate between a pixel height and height: auto, because they don't know the end value until layout runs. For years the workarounds were ugly: hard-code a max-height and hope your content never exceeds it, or measure the element in JavaScript and animate to that number.
CSS Grid sidesteps the whole problem. A grid row sized in fr units does interpolate smoothly. So we wrap the content in a single-row grid and transition that row from 0fr (zero share of the height) to 1fr (all of it). The browser is animating a flexible track, not an auto value, so it knows exactly what to tween between.
The one piece people miss: the inner element needs overflow: hidden. Without it, a grid item won't shrink below the size of its own content, so the 0fr row never actually collapses. With it, the content gets clipped as the track closes — giving you the clean reveal.
Here the toggle is a hidden checkbox driving a sibling selector, so the whole thing stays pure CSS and keyboard-accessible. Swap the checkbox for a class you toggle in JS if you'd rather control it from code — the animating CSS is identical either way.