跪拜 Guibai
← Back to the summary

That 12px Child Element Is 63px Tall Because of a Ghost Called Strut

Problem Introduction

<style>
  .parent {
    font-size: 42px;
    line-height: 1.5;
    background: lightgray;
  }
  .child {
    display: inline-block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  <div class="child">123456789</div>
</div>

Looking at the rendered output of the code above, does it look different from what you imagined? Why does my .child have mysterious whitespace around it when it clearly has no padding or margin?

If, at first glance, you also thought the entire .parent class's height should match .child's height and that the space above and below .child shouldn't exist, then it's recommended that you seriously learn about CSS's IFC (Inline Formatting Context) and strut.

The Source of the Whitespace

Let's first look at how that gray area's height is actually calculated:

So the discrepancy lies in the difference between 63 and 18, but the question is, .parent has no direct text node inside it; it only contains the text of .child. Why is it still stretched to the height of .parent?

The Support of Strut

Because .parent is a block-level container (<div>) and it internally contains an inline-level element (.child is set to display: inline-block), .parent establishes an IFC.

At this point, if .parent has text content, the browser will secretly insert a strut inside .parent, before .child. .parent's font-size is 42px and line-height is 1.5, so its height is 63px.

Now there are two elements participating in the layout inside .parent:

  1. An invisible strut with a height of 63px.
  2. The .child box with a height of 18px.

Because both default vertical-align values are baseline, the browser performs the following operations:

  1. Finds the baseline of the strut.
  2. Finds the baseline of .child (for inline-block, the baseline is the baseline of the last line of text inside it, which here is the baseline of "123456789").
  3. Aligns these two baselines.

Then you see the result you see: even though .child is so small, it gets stretched. This seems counter-intuitive in a scenario where there's only a single element inside the entire .parent, but imagine if a whole sentence contained a mix of multiple inline elements; having a unified line height is a perfectly normal design.

However, if an inline element inside is taller than the parent's line height, it will still be stretched...

<style>
  .parent {
    font-size: 30px;
    line-height: 1.5;
    background: lightgray;
  }
  .child {
    display: inline-block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
  .child2 {
    display: inline-block;
    font-size: 50px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content.
  <div class="child">123456789</div>
  This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content.
  <div class="child2">123456789</div>
  This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content. This is a very long piece of content.
</div>

Knowing that strut causes unexpected stretching, how do we fix it? A somewhat hacky method is to directly suppress the line height of .parent:

<style>
  .parent {
    font-size: 0;
    line-height: 0;
    background: lightgray;
  }
  .child {
    display: inline-block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  <div class="child">123456789</div>
</div>

But for a more reliable method, I think we need to start by eliminating the IFC.

IFC

Because of IFC, the line height inside a Div follows the Div's own font-size and line-height to produce a strut. So how does IFC itself arise?

In a nutshell: When a block-level container directly contains inline-level elements, an IFC is generated inside that block-level container.

What if both block-level and inline containers exist simultaneously? To prevent text and block-level elements from conflicting, the browser secretly creates an "anonymous block box" to wrap the text. At this point, an IFC is generated inside that "anonymous block box" containing the text, but the outermost <div> as a whole is not a pure IFC environment.

We understand that a strut only exists because of IFC, so to eliminate this strut, we just need to change the formatting context.

To handle IFC, CSS also has BFC, FFC, and GFC.

In CSS's visual formatting model, besides the most commonly heard BFC (Block Formatting Context) and IFC (Inline Formatting Context), there are also FFC and GFC.

In total, CSS mainly has the following four formatting contexts:

BFC (Block Formatting Context) is probably the most familiar. It is triggered whenever float is not none, position is absolute or fixed, display uses inline-block / table-cell / flex / grid / flow-root (the most common modern method), or overflow is not visible. Its characteristics are that internal elements are arranged vertically, margins do not overlap with the outside, it can contain floating elements, and it won't be covered by floating elements. Clearing floats, preventing margin collapse, and adaptive two-column layouts are all its jobs.

<style>
  .parent {
    font-size: 42px;
    line-height: 1.5;
    background: lightgray;
  }
  .child {
    display: block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  <div class="child">123456789</div>
</div>

Changing .child to block makes the text inside it generate its own IFC, solving the problem.

FFC (Flex Formatting Context) is created when display is set to flex or inline-flex. Child elements inside the container become flex items and no longer follow block or inline layout rules; space distribution and alignment on the main and cross axes are relatively free. It's suitable for one-dimensional layouts like navigation bars, centering cards, and distributing remaining space evenly.

GFC (Grid Formatting Context) is created when display is set to grid or inline-grid. The container is divided into rows and columns, forming grid cells; child elements can be placed anywhere, spanning rows and columns freely. Two-dimensional layouts like the main structure of an entire page or dashboards basically rely on it.

<style>
  .parent {
    display: grid;
    font-size: 42px;
    line-height: 1.5;
    background: lightgray;
  }
  .child {
    display: inline-block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  <div class="child">123456789</div>
</div>

After setting .parent to flex or grid, the child elements no longer form an IFC, solving the problem.

TL;DR

A strut exists because the parent element creates an IFC. It is essentially an invisible "text line" whose height is entirely determined by the parent element's own font-size and line-height. As long as there is inline-level content inside the parent element, that line is propped up, and even if you only put a tiny piece of text inside, the line height will be taller than you expected.

So the next time you encounter this kind of "mysterious whitespace" problem, you can first suspect it's an IFC issue. The corresponding fixes are:

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

LevinSword 1 likes

I often run into inexplicable whitespace... now I get it