CSS · Layout
Centering (all the ways)
⚡ TL;DR — "Centring a div" isn't one problem; it's a matrix of inline vs block, horizontal vs vertical, and known vs unknown size — and the reason the old tricks felt cursed is that
margin: autocentres horizontally in flow but resolves to zero vertically. Flexbox and Grid make the whole matrix collapse to one line.
🧠 Mental model
Before you pick a technique, ask three questions: which axis?, is the thing inline or block?, and do I know its size? Almost every "centring is hard" story is really one of these being answered wrong. text-align: center centres inline content (text, inline-blocks) inside a block. margin: auto centres a block horizontally by soaking up leftover inline space equally. Neither centres vertically in normal flow — because block layout distributes free space only on the inline axis, so auto on the top/bottom margins computes to 0.
The modern answer is to stop fighting normal flow: create a flex or grid formatting context, where free space is distributed on both axes, and auto margins and alignment properties finally work in two dimensions.
⚙️ How it actually works
The reason place-items: center "just works" is that flex and grid define explicit alignment along two named axes. In flexbox: justify-content aligns along the main axis, align-items along the cross axis. place-items is the shorthand for align-items + justify-items. The container measures the item, computes leftover space, and splits it — on both axes — which is exactly the capability normal block flow lacks vertically.
The absolute-position technique works differently and is worth knowing for the unknown-size case:
position: absolute;
top: 50%; /* move the box's TOP to the centre line */
left: 50%;
transform: translate(-50%, -50%); /* pull it back by half its OWN size */
The trick is that top: 50% is relative to the containing block, but translate(-50%, -50%) is relative to the element's own box — the one place percentages flip their reference. That's what makes it work without knowing the element's dimensions.
And the forgotten one — auto margins do centre vertically inside a flex or absolutely-positioned context, because those contexts give the vertical axis free space to distribute:
.parent { display: flex; }
.child { margin: auto; } /* centred both axes — auto now has space to eat */
💻 Code
/* The default answer for 95% of cases — one declaration, any size */
.parent {
display: grid;
place-items: center; /* align-items + justify-items, both axes */
}
/* Flexbox equivalent — reach for it when you also need direction/wrap */
.parent {
display: flex;
justify-content: center; /* main axis (row → horizontal) */
align-items: center; /* cross axis (row → vertical) */
}
/* Centre a block horizontally the classic way (no fl/grid needed) */
.card { max-width: 60ch; margin-inline: auto; }
/* Centre inline content (text, buttons) */
.banner { text-align: center; }
/* Overlay / unknown-size centring without touching the parent's display */
.badge {
position: absolute;
inset: 0;
margin: auto; /* auto on all sides + inset:0 → centred */
width: max-content; height: max-content;
}
⚖️ Trade-offs
place-items: centeris the right default, but it centres every direct child. If the parent holds one thing (a modal, an empty state), perfect; if it holds a list, you wantedplace-contentor per-item control.- Absolute +
translate(-50%,-50%)is the escape hatch for centring over other content (tooltips, spinners) without disturbing layout — but it removes the element from flow, so it can't push siblings and may sit on a half-pixel (blurry on non-retina; addwill-change/round if it bites). margin-inline: autoneeds a constrained width to have leftover space to distribute — on anauto-width block it does nothing, which trips people up.- Don't set a height and
line-heightequal to fake vertical centring anymore; it only works for a single line of text and breaks the moment content wraps.
💣 Gotchas interviewers probe
- Why doesn't
margin: autocentre vertically in normal flow? Because block layout only distributes free space on the inline axis; verticalautomargins compute to0. Naming this is a strong senior signal. justify-contentvsalign-itemsflip meaning whenflex-directionchanges. Incolumn,justify-contentbecomes vertical. Alignment is tied to main/cross, not horizontal/vertical.transform: translate(%)is element-relative, whereastop/left: %is containing-block-relative. That mismatch is why the-50%overlay trick works for unknown sizes.align-contentvsalign-items.align-itemspositions items within their line;align-contentdistributes multiple lines. On a single-line flex containeralign-contentdoes nothing — a classic red herring.- Text centring ≠ box centring.
text-align: centerwon't move a block child;margin: autowon't move inline text. Matching the tool to inline-vs-block is the whole game.
🎯 Say this in the interview
"I don't think of centring as one trick — I think about which axis, whether the thing is inline or block, and whether I know its size. For nearly everything now I use
display: grid; place-items: center, which centres on both axes for any size in one line. Flexbox withjustify-content/align-itemswhen I also need direction or wrapping — remembering those map to main and cross axis, so they swap when I go toflex-direction: column. For centring something over other content, like a spinner, I use absolute positioning withtop/left: 50%andtransform: translate(-50%, -50%), which works without knowing the size because the translate percentage is relative to the element's own box while top/left is relative to the container. The old pain was that plainmargin: autoonly centres horizontally in flow, because block layout only hands out free space on the inline axis."
🔗 Go deeper
- Josh Comeau — How to center a div — the definitive decision tree across every case.
- MDN — Box alignment — how
justify-*/align-*are defined against main/cross axes. - MDN —
place-items— the one-line both-axes shorthand.