023: The Box Model
Learning outcomes
By the end of this lesson, you should be able to identify an element's content, padding, border, and margin; calculate its dimensions under both sizing models; apply a global border-box setup; tell padding apart from margin; explain vertical margin collapse; and inspect box geometry in DevTools.
Prerequisites and retrieval
Start with the typographic portfolio. Recall why fixed text heights break when the page is zoomed and which properties inherit. Then make a prediction: does a child's padding inherit from its parent? It does not. The focus here is the rectangle that the browser generates for each visible element.
Terminology
- Content box: The innermost box area containing text, images, or child boxes. — Source: CSS Box Model 4
- Padding: The area between the content edge and border edge, painted with the background. — Source: MDN: The box model
- Border: The drawn edge area surrounding padding and content. — Source: MDN: The box model
- Margin: The always-transparent outermost area separating an element from siblings. — Source: MDN: The box model
content-box: Default box-sizing where declared width/height apply to content only. — Source: MDN: box-sizingborder-box: Box-sizing keyword where declared width/height include padding and border. — Source: MDN: box-sizing- Margin collapse: Adjoining vertical margins merging into one margin in normal flow. — Source: MDN: Mastering margin collapsing
- Outer size: The margin-box extent: border box plus margins. — Source: CSS Box Model 4
- Box edges: "Each box has four edges: content edge, padding edge, border edge, and margin edge." — Source: CSS Box Model Module Level 4: Box edges
- Logical properties: "Logical properties such as margin-block and padding-inline map to physical sides based on writing mode." — Source: MDN: Logical Properties
Mental model: nested rectangles
Think of the element as four rectangles nested from the inside out: content, padding, border, and margin. The background normally paints through the content and padding as far as the border edge, but it does not paint into the margin. That distinction is immediately useful: padding makes a clickable element's painted area and hit area larger, while margin only creates separation from neighboring elements.
Under the default content-box model, an element with width: 300px; padding: 20px; border: 2px has a 344px border box: 300 + 40 + 4. With border-box, the declared 300px already includes the content, padding, and border. Margins remain outside that declared width in either model.
A predictable stylesheet usually establishes the sizing rule near the beginning:
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
The universal selector also covers generated pseudo-elements, while inheritance lets the root element define the decision in one place. This rule does not make box dimensions irrelevant; it changes the sizing equation so component dimensions are easier to control.
Beginner example: dissect a project card
<article class="project-card">
<p class="project-card__meta">Accessibility · 2026</p>
<h2>Library finder</h2>
<p>Opening hours and directions presented clearly.</p>
<a class="project-card__link" href="#">Read case study</a>
</article>
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
body {
margin: 0;
padding: 1rem;
background: rgb(241 245 249);
font-family: system-ui, sans-serif;
line-height: 1.6;
}
.project-card {
max-inline-size: 32rem;
margin-block: 1.5rem;
padding: 1.25rem;
border: 2px solid rgb(203 213 225);
border-inline-start: 0.4rem solid rgb(37 99 235);
border-radius: 0.75rem;
background: white;
}
.project-card > :first-child { margin-block-start: 0; }
.project-card > :last-child { margin-block-end: 0; }
.project-card__link {
display: inline-block;
padding: 0.5rem 0.75rem;
border: 2px solid rgb(29 78 216);
border-radius: 0.35rem;
}
Select .project-card in DevTools and look at the box diagram. The center shows content dimensions; the surrounding bands represent padding, border, and margin. Turn padding off and the content moves toward the border. Turn margin off and the card moves toward its neighbors, but its white background does not grow. Toggle the border as well, then watch how border-box keeps a constrained inline size accounting for it.
The link uses inline-block so its vertical padding and border create a dependable visual control without forcing it onto its own block line. The next lesson, 025, covers display behavior in more depth.
Intermediate example: spacing and margin collapse
Adjacent block margins can collapse:
<section class="prose">
<h2>Process</h2>
<p>First, understand the content.</p>
<p>Then, test the interaction.</p>
</section>
.prose h2 { margin-block: 0 1rem; }
.prose p { margin-block: 1rem 0; }
The touching 1rem vertical margins do not necessarily create 2rem of space. In normal block flow they may collapse into a single margin. Horizontal, or inline, margins do not collapse in this way. Padding or borders can also stop a parent's and child's margins from meeting.
If you want the wrapper to own the spacing between siblings, use a flow pattern:
.flow > * { margin-block: 0; }
.flow > * + * { margin-block-start: 1rem; }
Add class="prose flow" to the section. The adjacent-sibling selector matches each direct child that comes after another direct child, so every gap has one declared source. This is optional: ordinary margins are perfectly valid when their collapse behavior is understood.
Here is a callout with both kinds of spacing:
.callout {
margin-block: 2rem;
padding: 1rem;
border: 1px solid rgb(147 197 253);
border-radius: 0.5rem;
background: rgb(239 246 255);
}
Padding supplies internal breathing room and is part of the area that receives the background. Margin places the callout away from the surrounding prose. Swapping the two changes both the grouping the reader sees and the area that is painted.
Optional advanced example: outline versus border
Focus indicators often use outline:
a:focus-visible {
outline: 3px solid rgb(234 88 12);
outline-offset: 3px;
}
Unlike a border, an outline generally does not participate in the element's box dimensions, so showing focus does not move neighboring content. It can overlap nearby content, however, so leave it enough room to remain visible. Avoid unnecessary overflow: hidden, which can clip the focus indicator.
Mistakes, debugging, and DevTools
- Assuming
width: 100%plus padding fits withcontent-box: it can overflow its parent. - Adding margins to enlarge a click target: margin is not part of the element's clickable painted box; use padding.
- Using negative margins to repair unexplained layout: inspect the box model and source of spacing first.
- Forgetting default heading/paragraph margins: browser CSS is visible in DevTools.
- Expecting vertical margins always to add: check collapse conditions.
- Removing outlines because they sit outside the border: replace them accessibly instead.
- Applying
overflow: hiddenjust to contain rounded corners and clipping focus or content.
Use the element picker and the Box Model panel. Hover each region to highlight the corresponding area on the page, and edit values directly; arrow keys make small increments easy. In Computed, verify box-sizing, used dimensions, and logical-to-physical mappings. If an element is wider than expected, inspect the parent's width, the element's own width, both inline padding values, borders, and margins.
Accessibility and performance
Padding is a practical way to create a comfortable target size. WCAG 2.2 Target Size (Minimum) generally calls for a 24 by 24 CSS pixel target or sufficient spacing, subject to its exceptions; larger controls are often easier to use. Do not force text into a fixed box by shrinking it. Let the box grow in the block direction.
Keyboard focus can extend beyond the border. Keep it visible, avoid clipping it, and maintain sufficient contrast. Logical properties such as padding-inline and margin-block adapt to writing modes and text direction.
Borders, padding, and rounded corners are inexpensive in typical interfaces. Very large blurred shadows can add paint work, particularly during animation or scrolling. Measure before optimizing; getting responsive geometry right is the priority.
Deep dive: calculate a box instead of guessing
With the default content-box model:
.card {
width: 20rem;
padding: 1rem;
border: 2px solid;
}
the declared width describes only the content box. The rendered border-box width is:
20rem + 2rem padding + 4px border.
With:
*,
*::before,
*::after {
box-sizing: border-box;
}
the declared width includes padding and border as well as content, so layout calculations are usually easier.
Do not treat border-box as a magic reset. The useful thing to understand is the changed sizing equation.
Deep dive: margin collapse
In normal block flow, vertical margins can collapse rather than add:
<section class="section">
<h2>Title</h2>
<p>Intro text.</p>
</section>
h2 {
margin-block-end: 2rem;
}
p {
margin-block-start: 1rem;
}
It is tempting to expect 3rem between the boxes. Because these are adjoining block margins, they can collapse, leaving the larger margin as the effective separation.
Margins do not collapse in the same way inside Flexbox or Grid. Establishing a new formatting context with display: flow-root can also change the result. That is why a component can appear to have inconsistent spacing after moving from normal flow into Grid: the layout mode changed the margin behavior.
One maintainable pattern is to make one layer responsible for the relationship:
.flow > * + * {
margin-block-start: 1rem;
}
The wrapper now manages sibling spacing instead of leaving both adjacent components to contribute overlapping margins.
Worked example: content-box bug versus border-box fix
<form class="signup">
<label for="email">Email</label>
<input id="email" type="email">
</form>
Bug:
.signup input {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #94a3b8;
}
With content-box, 100% describes the content width. Padding and border are then added outside it, so the input can extend beyond its parent.
Fix:
.signup input {
box-sizing: border-box;
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #94a3b8;
}
Now the declared width includes those additions. A global border-box rule is common because this is generally the sizing behavior interface authors expect.
Deep dive: border, radius, and outline have different jobs
.card {
border: 1px solid #cbd5e1;
border-radius: 0.75rem;
}
.card:focus-within {
outline: 3px solid #2563eb;
outline-offset: 3px;
}
The border is part of the box model. The outline is painted outside the border edge and normally consumes no layout space, which makes it particularly useful for focus indication.
Never replace a visible focus outline with outline: none unless you provide another focus indicator that is equally obvious.
Worked example: spacing ownership in a card
This arrangement is fragile:
.card h2 { margin-bottom: 1rem; }
.card p { margin-top: 0.75rem; margin-bottom: 1rem; }
.card a { margin-top: 2rem; }
A more predictable arrangement gives each layer a clear responsibility:
.card {
padding: 1.25rem;
}
.card__body > * {
margin: 0;
}
.card__body > * + * {
margin-block-start: 0.75rem;
}
.card__action {
display: inline-block;
margin-block-start: 1.25rem;
}
The card owns its outer padding, the body owns its vertical rhythm, and the action owns its separation from the body. That is easier to reason about than a set of overlapping margins distributed across every child.
Box-model debugging routine
When a component is unexpectedly too large:
- Inspect its box-model diagram in DevTools.
- Check
box-sizing. - Check intrinsic width of children.
- Check percentage widths plus padding/borders.
- Check margins outside the border box.
- Check
min-widthormin-heightconstraints. - Check whether a flex/grid item's automatic minimum size prevents shrinking.
Do not use overflow: hidden as a universal overflow fix. It can hide focus rings, menus, and genuine layout bugs instead of addressing the cause.
Tiered exercises
Checkpoint: diagnose spacing ownership
When a gap looks wrong, select both neighboring elements and decide who should own that relationship. Padding belongs to the component whose background needs breathing room. Margin separates components. If every child in a parent layout needs the same spacing, gap belongs to that parent. Avoid relying on two unrelated margins to produce an accidental total.
Run the box calculation twice. With inline-size: 20rem, inline padding of 1rem, and borders of 0.125rem, content-box produces a 22.25rem border box. With border-box, the border box remains 20rem and the content gets the remainder. Add margin-inline: 1rem; under border-box, the outer occupied width becomes 22rem because margin is never included. Then resize below 22rem and observe why the max constraint and the available-width relationship still matter.
Use DevTools to verify the arithmetic rather than relying on visual estimation. Fractional used values can appear because rem values, zoom, and device scaling resolve to subpixels. That is normal.
Create a nested card whose first child is a heading. Give the heading a block-start margin, then temporarily remove the card's padding and border. Observe whether the child margin appears outside the parent through margin collapse. Restore the padding and watch the boundary change. You do not need to memorize every collapse combination. Keep the rule that adjoining block margins in normal flow can combine, then inspect when the space appears outside its apparent owner. Flex and Grid containers have different margin behavior, so changing layout modes can change spacing unexpectedly.
Finally, compare border and outline while focusing the card link. A thicker border changes geometry unless the dimensions account for it; an outline paints without consuming layout space. That makes outline a strong default focus mechanism, as long as it is not clipped and remains visible against neighboring colors.
Foundation: Draw and label the four box layers. Calculate the border-box width of a 240px content-box with 16px inline padding and 2px borders.
Core: Apply the global border-box setup. Build a card with internal padding, external margin, border, radius, and a padded link. Explain why each spacing property was chosen.
Stretch: Create a .flow section with exactly one consistent block gap between direct children. Demonstrate margin collapse before and after and inspect both in DevTools.
The content-box total is 240 + 16 + 16 + 2 + 2 = 276px.
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
.project-card {
max-inline-size: 32rem;
margin-block: 1.5rem;
padding: 1.25rem;
border: 2px solid rgb(203 213 225);
border-inline-start: 0.4rem solid rgb(37 99 235);
border-radius: 0.75rem;
background: white;
}
.project-card__link {
display: inline-block;
padding: 0.5rem 0.75rem;
border: 2px solid currentColor;
border-radius: 0.35rem;
}
.flow > * { margin-block: 0; }
.flow > * + * { margin-block-start: 1rem; }
.project-card__link:focus-visible {
outline: 3px solid rgb(234 88 12);
outline-offset: 3px;
}
Recap and exit questions
Every rendered element generates nested content, padding, border, and margin areas. border-box makes declared sizing include padding and border. Use padding for internal space and clickable area; use margin for separation between elements.
- What contributes to a
content-boxelement's total width? - Does margin receive the element's background?
- Why does padding enlarge a target but margin does not?
- Which margins can collapse?
- Why is outline useful for focus?
