The CSS contain property and BFC
2025-08-25 #CSS

The other day I saw this post about how a simple CSS animation: transition: height 300ms ease-in-out leads to using 60% of the CPU of a M2 MacBook which is quite a performance problem. And it turns out that the height property is a layout property, which means that every change of it triggers a layout recalculation then re-painting and re-compositing, making it one of the most expensive CSS properties to animate. The solution from the original author is to switch from changing the height to use transform, a composite property that only triggers a cheap re-compositing.

Modern solution

Although the story is both interesting and educational, I felt there was something wrong about this whole thing. How is it that modern browsers can’t even properly animate height property without recalculating the whole web page? After reading through the comments under the HN post, I found the contain property which is perfectly suited for this problem. This CSS property basically tells the browser that changes inside the container only trigger recalculation inside of this DOM subtree as opposed to the entire page. From MDN about the benefit of using contain:

The main benefit of containment is that the browser does not have to re-render the DOM or page layout as often, leading to small performance benefits during the rendering of static pages and greater performance benefits in more dynamic applications.

BFC

Definition

Setting the contain property to some values also creates a new block formatting context. It does makes a lot of sense as BFC is like a weaker version of contain. Common methods of creating BFC include: setting overflow to any value other than visible or clip, positioning the element absolutely, or becoming flex or grid items as direct children of flex or grid container. The modern approach is using display: flow-root.

The definition of BFC on MDN is:

It’s the region in which the layout of block boxes occurs and in which floats interact with other elements.

I always find this introduction way too abstract and hard to understand. Instead of trying to understand all that fancy words, it is easier to just remember the effects of BFC:

contain internal floats exclude external floats suppress margin collapsing

The first two effects are pretty easy, just as the name implies, BFC will prevent:

  • internal floats poke out of the element
  • external floats overlap with the element

Margin collapsing

In some situations, the top and bottom margins collapse into each other resulting in a single margin, the final margin is just the largest margin of the two. This behavior occurs in three basic cases:

  • adjacent siblings
  • parent and child with no content in between: the margin-top of parent and the margin-top of its first child, margin-bottom and the last child.
  • empty blocks: its own margin-top and margin-bottom

Note that these basic cases can be combined into more complex situation, and may even involve negative margins.

References

granola.ai: Don’t animate height

HackerNews: Don’t animate height

MDN: contain

MDN: Block formatting context

MDN: margin collapsing

[This Space Intentionally Left Blank] inspired by cpu.land

The bottom of every blog post is padded so readers can maintain a consistent eyeline.