CSS Layout Without Fighting the Box Model All Week

You add width: 100% and the page grows a scrollbar. You set height: 100% and nothing happens. You center a div with four different tricks and it is still 3 pixels off. You conclude CSS is broken. CSS is consistent. Your mental model is from 2009 plus a Stack Overflow answer that used floats.

Layout in modern CSS is mostly the box model, flex, grid, and the fact that percentages need a defined parent. This is the model that stops the week-long fight.

The box is content, padding, border, margin

By default, width is the content width. Padding and border add on. That is why width: 100% plus padding overflows. box-sizing: border-box makes width include padding and border. Put it on * in a reset and move on. This is not optional if you value your time.

Margin collapses vertically between siblings and with parents in specific cases. It does not collapse horizontally. It does not collapse on flex/grid children the same way. If two headings stick together, it is collapse. Use padding on the parent, or flex with gap, which does not collapse.

overflow on a parent can clip margins and create a scroll container. overflow-x: hidden on body to hide a bug will clip focus rings. Find the overflowing child.

Block, inline, and why height 100% failed

A block box takes the width of the parent and stacks. An inline box sits in text. height: 100% means 100% of the parent’s height, but the parent’s height is often auto, which is “as tall as the children.” Percentage of auto is nothing. The chain of heights from html, body { height: 100% } is the old full-page trick. Today, min-height: 100vh or min-height: 100dvh on the layout wrapper is clearer. dvh accounts for mobile browser chrome. vh can hide content behind the URL bar.

min-height: 100% has the same parent problem.

Flexbox for rows and alignment

display: flex on the parent. Children become flex items. justify-content is the main axis (row: horizontal). align-items is the cross axis (row: vertical). People swap these and then fight.

flex: 1 means grow. A child with a long word can still overflow. min-width: auto on flex items is a common overflow source; min-width: 0 on the child that should shrink lets it.

gap is for spacing. Prefer gap over margin on children when you can. Wrapping: flex-wrap: wrap. A row that cannot wrap will overflow. That is the mobile bug.

Do not use flex for a 12-column page unless you like pain. That is grid.

Grid for two dimensions

display: grid. grid-template-columns: 1fr 300px is a sidebar. repeat(auto-fit, minmax(16rem, 1fr)) is a responsive card grid without a media query for every step.

fr is leftover space. minmax(0, 1fr) avoids the min-content overflow trap, similar to flex’s min-width: 0.

Alignment on the grid is justify-items / align-items for cells, justify-content / align-content for extra space in the grid. If you mix them up, a mental sketch of the grid on paper helps more than another tutorial.

Positioning: relative, absolute, fixed, sticky

relative is a positioning context. absolute is relative to the nearest positioned ancestor, not always the parent you think. If nothing is positioned, it is the viewport (more precisely the containing block). That is why a dropdown appears in the corner.

fixed is viewport (unless a transform on an ancestor creates a containing block—this surprise is famous). sticky needs a threshold (top: 0) and an ancestor that is not overflow: hidden. Sticky failing is almost always overflow on a parent.

Do not absolute-position your entire layout. You will fight scrolling and accessibility.

Images and media

img { max-width: 100%; height: auto; } prevents the classic overflow. Give width and height attributes to reduce layout shift. object-fit: cover for crops.

Iframes need a wrapper. They do not care about your grid.

Specificity without a war

IDs beat classes. Inline beats IDs. !important is a last resort. Nesting in CSS modules or a BEM-ish class on the component beats a global .btn override chain.

If you cannot override a style, DevTools shows the winning rule. Read it. Do not add more !important.

A debug order for “the layout is wrong”

  1. Inspect the overflowing element. Outline it.
  2. Check box-sizing and padding on width: 100%.
  3. Check min-width on flex children.
  4. Check percentage heights and parent height.
  5. Check overflow on ancestors for sticky/fixed bugs.
  6. Check a positioned ancestor for absolute surprises.

This order fixes most week-long fights in an hour.

Media queries and container queries

Viewport media queries (@media (min-width: 768px)) are still the workhorse. Container queries let a card respond to its column, not the screen. Use them when the same component lives in a sidebar and a main column. Do not replace every media query on day one. Support is good now; your design system might not be ready.

Prefer rem for type and spacing if you care about user zoom. px for borders is fine. Mixing without a scale is how the design looks “off” without a single smoking gun.

Forms and overflow

Inputs with width: 100% plus padding overflow without border-box. Select elements have OS min widths. Tables need a strategy: wrap, scroll the table, or stack cells. A pricing table that looks perfect at 1440 and explodes at 360 is a layout bug, not a “mobile isn’t our audience” opinion if you take phones.

Z-index and stacking contexts

A modal under a header is a stacking context issue, not a bigger z-index contest. transform, opacity, and filter create contexts. The dropdown in a table that clips is overflow: hidden on a parent. Raise z-index until 9999 is a smell. Find the context.

Logical properties

margin-inline-start instead of margin-left helps RTL. You do not need to convert a whole app at once. Use logical properties on new layout. If the product is English-only forever, physical properties still work. Do not pretend you internationalized because you used one start.

The 100vh mobile trap

height: 100vh on a hero can hide the CTA under the browser chrome. 100dvh or a flex column with min-height and a scrollable middle is kinder. Test on a real phone. Checkers help; thumbs confirm.

Subgrid and when to wait

CSS subgrid is useful for aligning cards across a row. If your support matrix includes old browsers, it may not be the first tool. Grid on the parent and flex inside the card still ships. Use subgrid when you can test the browsers you claim.

Print and px

For screen layout, rem and flex/grid do the job. For a print stylesheet, you may want cm and a simple block flow. Do not design the app in print units. A @media print that hides nav is enough for most admin tools.

Auto margins for centering

margin-inline: auto on a block with a width still centers. Flex margin: auto on an item can eat leftover space in surprising ways. If a button flies to the edge, you used auto margin in a flex row. That is a feature. It is also a bug if you did not mean it.

Artikals is for this because frontend interviews still ask flex vs grid, and production still dies on min-width: auto. Learn the box, then flex, then grid, then positioning. The tricks from 2012 are optional. gap and border-box are not.

Leave a Reply

Your email address will not be published. Required fields are marked *