I was finishing the FAQ block on a client landing page, the classic accordion: click a question, the answer slides down. I built it with the approach I trust most for height animation: an animated max-height, plus overflow: hidden so the content does not spill while collapsed. Open and close felt smooth. Done, I thought.
Then the client sent a screenshot. Between the collapsed questions there was a sliver of text peeking through, a thin line where there should have been nothing. And more annoyingly, each collapsed item still ate vertical space it had no business occupying. The panel I assumed was 0 tall was not actually 0.
My first instinct was wrong. I figured the max-height was not really landing on 0, so I opened DevTools and checked the computed value. It was genuinely max-height: 0px. Then I figured a row of the answer was escaping the animation. It was not. The element was properly collapsed, its computed max-height was zero, yet the box still had height. That is a strange combination, and it was exactly the clue.
Why this happens
The misunderstanding came down to one assumption I had carried for years: that overflow: hidden clips right at the edge of the element. It does not. overflow: hidden clips at the padding-box, not the content-box. That means the element's own padding sits inside the clipped region, not outside it.
So the moment my answer panel had vertical padding — say the padding: 1rem 0 I set in one rule to make the open answer breathe — that padding did not disappear when max-height went to 0. max-height: 0 constrains the content-box height to zero, but the top and bottom padding still exist outside the content-box and still get rendered. overflow: hidden does not help, because the padding lives inside the padding-box, the very region the clip treats as safe. The result: the box's total height became padding-top + 0 + padding-bottom, not zero. And because the content-box was only squeezed to zero rather than truly emptied, a sliver of the top text line still managed to show through that padding gap before it clipped.
Once that clicked, everything made sense. The panel was never really zero because I had only zeroed one of two height components. What leaked was not escaping content — it was padding I had explicitly told to stay.
The fix
The fix is not to swap out overflow: hidden. The fix is to make sure nothing is left inside the clip region while the panel is closed, and that means the padding has to be zero in the collapsed state too. Not just max-height.
So I moved the padding into the open state only. When collapsed, padding: 0. When open, the padding appears together with max-height:
.faq-item .answer {
max-height: 0;
overflow: hidden;
padding: 0;
transition: max-height 0.3s ease, padding 0.3s ease;
}
.faq-item.open .answer {
max-height: 500px;
padding: 1rem 0;
}Two things matter here. First, padding: 0 in the default state, not just max-height. Second, padding is animated alongside max-height in the transition property, so on toggle both move together and there is no padding jump appearing abruptly before the height catches up. The moment I deployed this, the peeking thin line was gone, and every closed item genuinely stopped taking vertical space. The box was truly zero.
If you want to go cleaner and stop guessing the 500px number, I now often reach for grid-template-rows: 0fr to 1fr for automatic height animation. But the padding trap is identical: anything with vertical padding inside the clipped track still has to be zeroed when closed. The principle does not change, only the mechanism.
The takeaway
overflow: hidden does not mean "hide everything past the element's edge" — it means "clip at the padding-box." As long as there is vertical padding on an element you think is zero, that box will never truly be zero, and the top of the content can peek through the padding gap before it clips. If you animate max-height for a collapse, zero the padding in the closed state too and animate both together. Since then, whenever I build an accordion, I stop thinking only about content height and start asking: what else is still tucked inside this clip region?
