skip to content

In CSS, why does `height: 100%` on an element often have no effect while `width: 100%` works, and what do percentage paddings resolve against?

level: middleimportance: should knowfreq 58%

answer

  1. percentage means something different per property
  2. the reference must be definite
  3. auto-height parent, circular dependency
  4. inline size for all four paddings
  5. flex stretch or a viewport unit instead

basics

~20 s

A percentage height needs a definite height on the containing block; when that ancestor is auto-height, the percentage behaves as auto. Widths work because block layout always gives a definite inline size. Percentage padding and margin resolve against the containing block's inline size, on every side.

solid answer

~50 s

Percentages are not a unit with a fixed meaning — each property says what its percentage resolves against. For `width`, it is the containing block's inline size, and in normal block layout that size is always definite, so `width: 100%` reliably works. For `height`, it is the containing block's block size, and if that ancestor's height is `auto` — sized by its own content — there is nothing definite to take a percentage of, so the declaration effectively resolves to `auto` and the element just hugs its content. That is why the old fix was `html, body { height: 100% }` all the way down the chain. Modern options are better: use a viewport unit such as `100dvh`, or make the parent a flex or grid container so the child stretches. The other trap: `padding` and `margin` percentages resolve against the containing block's **inline** size on all four sides, so `padding-top: 50%` is half the *width*, not the height.

code

css · 13 lines
css
/* Fails: .box hugs content because .wrap has auto height */
.wrap { }
.wrap > .box { height: 100%; }

/* Fix A: say what you meant */
.box { min-height: 100dvh; }

/* Fix B: let the parent stretch it, no percentage needed */
.wrap { display: flex; min-height: 100dvh; }
.wrap > .box { flex: 1; }

/* Percentage padding always uses the INLINE size */
.ratio { padding-top: 50%; } /* half the WIDTH, not the height */

go deeper

for a junior

Recall that a percentage height needs its parent to have a real height, and that the quick way to fill the window is a viewport unit rather than a chain of 100% rules.

for a middle

Explain definiteness: an auto-height ancestor makes the percentage circular so it resolves to auto, and state that percentage padding on all four sides uses the containing block's inline size.

for a senior

Demonstrate debugging instinct — walk the ancestor chain to the first auto height, then choose the right fix for the codebase: viewport unit, flex/grid stretch, or an explicit height contract, and say why chained 100% is brittle.

for a principal

Frame it as an API question for the layout system: percentage heights create an implicit contract between distant ancestors, which is precisely the coupling a component library should refuse. Argue for intrinsic sizing and stretch as the default idiom.

## Percentages are per-property, not a unit A percentage in CSS has no meaning on its own. Every property that accepts one defines its own reference: `width: 50%` means half the containing block's inline size, `font-size: 120%` means 1.2 times the parent's font-size, `background-position: 50%` means something else entirely (a position within the difference between the box and the image). Treating `%` as a single unit is the root of most confusion here. The *containing block* is the rectangle a box's percentages and offsets resolve against. For an in-flow element it is the content box of the nearest block-level ancestor; for an absolutely positioned element it is the padding box of the nearest positioned ancestor. ## Why width: 100% is reliable In block layout, the inline size (width in a horizontal writing mode) flows *downwards*: the viewport gives the root a width, the root gives its children a width, and so on. By the time a child asks "what is 100% of my containing block", the answer is already known. It is a **definite** size, so the percentage resolves. ## Why height: 100% so often does nothing Block size works the other way round. A block-level box with `height: auto` is sized **by its content** — it cannot know its height until its children are laid out. If a child then asks for 100% of that height, the request is circular: the parent's height depends on the child, and the child's height depends on the parent. CSS resolves the circularity by rule: when a percentage height's containing block has a height that depends on content, the percentage is treated as `auto`. So the element falls back to hugging its content, and the declaration looks like it was ignored. It was not — it resolved to `auto`. ```css /* .child renders at content height, not full window height */ .parent { /* height is auto */ } .parent > .child { height: 100%; } ``` The classic diagnosis: walk the ancestor chain up to `<html>` and find the first ancestor with an `auto` height. Everything below it is affected. ## The fixes, oldest to newest **Chain explicit heights.** The traditional fix is `html, body { height: 100% }`, then a definite height on every ancestor down to the element. It works, but it is brittle — one un-styled wrapper in the middle breaks the chain, and it turns a layout question into a whole-document contract. **Use a viewport unit.** If what you actually meant was "as tall as the window", say so: `min-height: 100dvh` (or `100vh`). This skips the ancestor chain entirely and is usually the honest expression of the intent. **Let flex or grid stretch it.** A flex item's default `align-items: stretch` makes it fill the container's cross size, and a grid item stretches into its track, without any percentage at all. As a bonus, once a flex or grid container has a definite size, percentage heights *inside* it start resolving, because the item now has a definite block size. **Absolute positioning.** For an absolutely positioned element, the containing block's height is definite by construction, so `height: 100%` — or `inset: 0` — resolves without help. This is why the same declaration that fails in flow suddenly works once you add `position: absolute`. ## Percentage padding and margin resolve against the inline size This is the second half of the question and it surprises people every time: **all four** percentage `padding-*` and `margin-*` values resolve against the containing block's **inline** size — the width in a horizontal writing mode. `padding-top: 10%` is 10% of the width, not the height. That is not a bug; it is what makes percentage padding usable, since resolving vertical padding against a content-derived height would be circular in the same way `height: 100%` is. It is also the mechanism behind the old "padding-bottom percentage" box trick used for fixed-proportion boxes before `aspect-ratio` shipped — the ratio came from the width because the padding resolved against the width. In a vertical writing mode the same rule holds against the *inline* axis, which is now the vertical one. Stating the rule in logical terms ("inline size") rather than "width" is the version that stays correct. ## Related percentage traps - **`height: 100%` on `<body>`** does nothing unless `<html>` also has a height, because `<body>`'s containing block is the root element's box. - **`min-height` percentages** follow the same definiteness rule as `height`. - **`transform: translateY(50%)`** resolves against the *element's own* border-box height, not the containing block — a different reference again, and the reason the centring trick with `translate(-50%, -50%)` works regardless of ancestors. - **`background-size: 100%`** resolves against the background positioning area, not the containing block. The habit worth building: when a percentage misbehaves, ask two questions in order — *what does this property say the percentage resolves against*, and *is that reference definite?*

  • Why does the same height: 100% suddenly work once you add position: absolute?
    Because the containing block changes. An absolutely positioned element resolves percentages against the padding box of its nearest positioned ancestor, and that box's height is definite by the time the element is laid out — there is no content-dependency loop. In normal flow the ancestor's `auto` height still depends on the child, so the percentage falls back to `auto`.
  • Why do percentage margins and paddings use the inline size even on the top and bottom?
    Because resolving vertical padding against a content-derived height would be circular: the parent's height would depend on padding that depends on the parent's height. Anchoring all four to the inline size — which block layout resolves first and independently — makes the value always computable. It also gave us the old padding-percentage ratio box.
  • Does putting the parent in flex layout make percentage heights inside it resolve?
    Often yes. Once the flex container itself has a definite block size, a stretched flex item gets a definite size too, so percentage heights on its children have something real to resolve against. But the cleaner move is usually to drop the percentage and let `stretch` or `flex: 1` do the sizing directly.

saying these in an interview costs you the question

  • Says height: 100% is simply broken or buggy in browsers
  • Believes padding-top: 10% is 10% of the element's height
  • Thinks percentages always resolve against the parent's width
  • Claims setting height on body alone is enough
  • Cannot name any alternative to chaining html, body heights

context