CSS height and width
The width and height properties look like the simplest properties in CSS — just give an element a size, right? In practice they interact with the box model, the element’s display type, its containing block, and companion properties like box-sizing, min-width/max-width, and min-height/max-height in ways that trip up beginners and experienced developers alike. This lesson covers exactly what a length or percentage value resolves against, how the browser computes the final rendered box, and how to size elements so your layouts stay predictable when content or viewport size changes.
Overview: How width and height Work
Every element on the page generates one or more boxes made of four layers: content, padding, border, and margin. By default, the width and height properties size only the innermost layer — the content box — not the padding or border. That default behavior is controlled by the box-sizing property, whose initial value is content-box. When you set box-sizing: border-box instead, width and height size the content box plus padding plus border together, which is usually what people intuitively expect.
width and height only apply to block-level boxes, inline-block boxes, and replaced elements (such as <img>, <video>, <canvas>, and form controls). They have no effect on non-replaced inline elements like <span>, <em>, or <a> unless you change their display to inline-block, block, or similar — an inline box’s dimensions come entirely from its content and line box, not from explicit sizing.
The default value of both properties is auto, but auto means something different for each axis. For a block-level element, width: auto makes the element fill the available width of its containing block (its parent’s content box, roughly). height: auto, on the other hand, makes the element shrink-wrap to the height of its content — there is no equivalent ‘fill the container’ behavior for height by default, which is the root cause of many height-related layout problems.
Percentage values are the other major source of confusion. A percentage width resolves against the width of the containing block, which is almost always well-defined (every element has an ancestor chain that eventually reaches the viewport). A percentage height, however, resolves against the height of the containing block only if that containing block has an explicit (non-auto) height. If every ancestor up the chain has height: auto, a percentage height computes to nothing and is effectively treated as auto — the single most common ‘why doesn’t my percentage height work’ bug in CSS.
Syntax
selector {
width: value;
height: value;
}
Both properties accept the same category of values:
| Value type | Example | Meaning |
|---|---|---|
| Length | width: 320px |
A fixed size in absolute (px) or relative (rem, em, vw, vh) units. |
| Percentage | width: 50% |
Relative to the containing block’s corresponding dimension. |
auto |
height: auto |
Browser computes the size (fill available width for block elements, shrink-to-fit content for height). |
min-content |
width: min-content |
The smallest size the content can be without overflowing (e.g. the widest unbreakable word). |
max-content |
width: max-content |
The size the content would take with no wrapping at all. |
fit-content() |
width: fit-content(400px) |
Uses available space up to the given limit, but no larger than max-content. |
clamp() |
width: clamp(200px, 50%, 600px) |
A fluid value that scales with a preferred value but never goes below the minimum or above the maximum. |
Companion properties min-width, max-width, min-height, and max-height take the same value types and constrain whatever width/height would otherwise compute to — max-width always wins over a larger width, and min-width always wins over a smaller one, even over max-width if the two conflict.
Examples
Example 1: box-sizing changes what width means
.box {
width: 300px;
height: 150px;
padding: 20px;
border: 5px solid #333;
box-sizing: border-box;
background: #f0f4ff;
}
Result: A light-blue rectangle whose total rendered size — including its dark 5px border — is exactly 300px by 150px. The 20px of padding on each side and the border are carved out of that declared size rather than added on top of it.
Because box-sizing is set to border-box, the browser first reserves 5px for the border and 20px for the padding on every side, then gives whatever space is left (300 – 10 – 40 = 250px wide, 150 – 10 – 40 = 100px tall) to the actual content. If box-sizing had been left at its default content-box, the same rules would instead produce a box that is 300 + 40 + 10 = 350px wide and 150 + 40 + 10 = 200px tall — 50px bigger in each dimension than the numbers on the label suggest.
Example 2: percentages need a sized containing block
.container {
width: 600px;
height: 400px;
background: #eee;
}
.panel {
width: 50%;
height: 50%;
background: steelblue;
}
Result: A 300px by 200px steel-blue rectangle appears inside the top-left corner of a 600px by 400px light grey box.
Both percentages here resolve cleanly because .container has explicit pixel dimensions for both width and height. .panel‘s width: 50% resolves to 300px (50% of 600px) and its height: 50% resolves to 200px (50% of 400px). If .container had height: auto instead of height: 400px, .panel‘s percentage height would fail to resolve and the element would fall back to its content’s natural height.
Example 3: a fluid, responsive card
.card {
width: clamp(240px, 50%, 480px);
min-height: 180px;
max-width: 480px;
padding: 1.5rem;
box-sizing: border-box;
border-radius: 8px;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.card img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
display: block;
}
Result: The card’s width scales fluidly with its container — never narrower than 240px, never wider than 480px, and tracking 50% of the container in between. It always has at least 180px of height but grows taller automatically if its content (like wrapped text) needs more room. The image inside always fills the card’s full width while keeping a 16:9 aspect ratio, with any excess image cropped off by object-fit: cover.
This combines several sizing tools at once: clamp() gives fluid width without a media query, min-height guarantees a baseline size without preventing growth (unlike a fixed height, which would clip overflowing content), and aspect-ratio replaces the old ‘padding-top percentage hack’ for keeping an image’s proportions consistent regardless of its actual pixel dimensions.
Under the Hood: How the Browser Computes a Box’s Size
When the rendering engine lays out an element, sizing happens in roughly this order:
1. It determines the element’s containing block — for a statically or relatively positioned element, this is the content box of its nearest block-level ancestor (or the padding box, depending on the spec detail); for an absolutely positioned element, it’s the padding box of its nearest positioned ancestor.
2. It resolves width and height against that containing block. Lengths are absolute. Percentages multiply the containing block’s corresponding dimension — but only if that dimension is itself definite (not auto).
3. It applies min-width/max-width and min-height/max-height as constraints on the result from step 2. The algorithm effectively clamps the value: if the computed width is smaller than min-width, min-width wins; if it’s larger than max-width, max-width wins; and min-width beats max-width if the two are contradictory.
4. It applies the box-sizing rule to decide whether that final number describes the content box alone (content-box) or the content-plus-padding-plus-border box (border-box), then works out how much room is left for actual content by subtracting padding and border where required.
5. For block-level elements with height: auto, height instead flows the other direction: the browser first lays out the content (and any in-flow children) to find their natural height, then that becomes the element’s height — this is why adding more text or an extra child can silently grow a box that has no explicit height.
Specificity and the cascade still apply to width and height like any other property: the most specific selector wins, and later rules of equal specificity override earlier ones. There’s nothing special about how the cascade treats sizing properties, but because sizing bugs are so visually obvious, a width or height accidentally overridden by a more specific rule is often the first place to check when debugging.
Common Mistakes
Mistake 1: expecting a percentage height to work without a sized ancestor
.parent {
width: 300px;
}
.child {
height: 50%;
background: tomato;
}
Why it’s wrong: .parent never declares a height, so its height computes to auto — sized entirely by its own content. A percentage height on .child has no definite number to multiply against, so the browser treats it as auto too, and .child collapses to whatever height its own content needs (often 0, if it’s empty).
.parent {
width: 300px;
height: 200px;
}
.child {
height: 50%;
background: tomato;
}
Corrected: giving .parent an explicit height: 200px gives .child‘s 50% something concrete to resolve against, so .child renders at exactly 100px tall.
Mistake 2: forgetting that content-box adds padding and border on top of width
.box {
width: 200px;
padding: 20px;
border: 10px solid black;
}
Why it’s wrong: with the default box-sizing: content-box, the declared width: 200px describes only the content area. The rendered element actually occupies 200 + 20 + 20 + 10 + 10 = 260px of horizontal space — 60px more than the number in the CSS suggests, which throws off grid and flex layouts built around that 200px assumption.
.box {
width: 200px;
padding: 20px;
border: 10px solid black;
box-sizing: border-box;
}
Corrected: adding box-sizing: border-box makes the 200px include the padding and border, so the element occupies exactly 200px total and the content area shrinks to 140px to make room.
A related trap is applying width to a naturally inline element like <span> or <a> and finding it has no effect at all — non-replaced inline elements ignore width and height entirely until you set display: inline-block or display: block.
Best Practices
- Set
box-sizing: border-boxglobally (commonly via a universal selector or a CSS reset) sowidth/heightalways describe the full visible box, including padding and border. - Prefer
max-widthover a fixedwidthfor containers and images so they shrink gracefully on narrow viewports instead of causing horizontal overflow. - Prefer
min-heightoverheightwhenever content length can vary — a fixedheightclips or overlaps overflowing content, whilemin-heightguarantees a floor and lets the box grow. - Use
clamp()for fluid sizing that scales between a minimum and maximum without writing a media query for every breakpoint. - Use
aspect-ratioinstead of the old padding-percentage hack when you need an element (especially an image or video placeholder) to keep fixed proportions at any width. - Remember that percentage heights require a definite-height ancestor; if you need a percentage-height child, make sure some ancestor in the chain has an explicit height, or restructure with Flexbox/Grid, which define their own sizing rules for children.
- Avoid setting both a fixed
width/heightand large padding/border withoutbox-sizing: border-box— the two combine unpredictably otherwise.
Practice Exercises
Exercise 1: Write a rule for .thumbnail that is exactly 150px wide and 150px tall in total (including a 2px border and 10px of padding on every side), using box-sizing so the numbers add up correctly. What is the resulting content-area size?
Exercise 2: A .sidebar element has height: 80% but never appears to take up 80% of anything — it always shrinks to fit its own content. Explain what’s missing from the CSS (you don’t need to see the parent’s rule to diagnose it) and write the fix.
Exercise 3: Create a .hero element whose width scales fluidly between 320px and 900px, tracking 80% of its container in between, with a min-height of 240px so it never gets too short even with little content. Use clamp() for the width.
Summary
widthandheightsize the content box by default;box-sizing: border-boxmakes them size content + padding + border instead.- They only apply to block, inline-block, and replaced elements — non-replaced inline elements ignore them.
width: autofills the containing block;height: autoshrink-wraps to content — the two axes behave differently by default.- Percentage widths resolve against the containing block’s width, which is almost always definite; percentage heights need an ancestor with an explicit (non-auto) height, or they silently fail.
min-width/max-widthandmin-height/max-heightconstrain the computed size, withmin-*always winning over a conflictingmax-*.- Modern fluid tools like
clamp()andaspect-ratioreduce the need for fixed pixel values and media-query breakpoints.
