Centering a div in CSS is the process of positioning an element in the middle of its parent container, horizontally, vertically, or on both axes, using layout properties like flexbox, Grid, or absolute positioning with a transform. Every guide on the topic shows you the same five snippets, and every one of them works on a box that fits. Almost none says what happens when it doesn't. So I measured it: 12 centering techniques, four box sizes, three browser engines. Flexbox sits at roughly 99% global support and Grid at about 97% as of 2026 per MDN, so compatibility isn't the question any more. The question is which technique quietly eats the top of your content.
Flexbox is a one-dimensional CSS layout model that arranges children along a single main axis and lets you align them on the cross axis with a single declaration. CSS Grid is a two-dimensional layout system that lays elements out across both rows and columns at once, which is why place-items: center can center on both axes with one line.
How do you center a div horizontally?
Horizontal centering has three answers depending on what you're centering. For a block-level element with a set width, margin: 0 auto is the classic move and it still works everywhere. According to the MDN box alignment documentation, three separate mechanisms cover horizontal centering: margin distribution in normal flow, text alignment for inline content, and flex or grid alignment properties for flex and grid children. Picking the wrong one is the single most common centering mistake, because each mechanism only works on a specific kind of box.
.card {
width: 320px;
margin: 0 auto; /* left and right margins share leftover space */
}
For inline or inline-block content, text-align: center on the parent pushes everything to the middle of the line box. For a flex child, justify-content: center centers along the main axis. Which one do you pick? If the element has an explicit width and lives in normal flow, use margin: auto. Otherwise, let a flex parent do it. One catch: margin: auto needs that width to be less than the container, otherwise there's no leftover space to split and nothing appears to move.
.toolbar {
display: flex;
justify-content: center; /* centers children along the row */
}
How do you center a div vertically?
This is the one that made people cry for years. In plain document flow you can't just say "put this in the vertical middle," because block layout distributes height differently than width. The modern fix is to make the parent a flex or grid container and align on the cross axis.
.parent {
display: flex;
align-items: center; /* vertical centering, finally */
height: 400px;
}
The real win is that the browser measures the child for you, so content of any height stays centered. No top: 50%; margin-top: -140px arithmetic that breaks the moment the copy changes length.
How do you center on both axes at once?
Three approaches cover almost every case. Grid is the shortest, one property for both axes:
.parent {
display: grid;
place-items: center; /* horizontal + vertical in one line */
min-height: 100dvh;
}
Flexbox needs two properties but reads clearly and plays nicely with sibling elements:
.parent {
display: flex;
justify-content: center; /* horizontal (main axis) */
align-items: center; /* vertical (cross axis) */
min-height: 100dvh;
}
Absolute positioning with a transform is the fallback for when you can't touch the parent's display mode, like centering an overlay or a modal on top of existing layout:
.overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* pull back by half its own size */
}
The transform works because top: 50% moves the element's top edge to the middle, then translate(-50%, -50%) shifts it back by half its own width and height. The parent needs position: relative, or the offsets resolve against the wrong ancestor. For a deeper look at when a two-dimensional grid beats a one-dimensional flex row for the surrounding layout, see CSS Grid vs Flexbox.
All three look identical on a box that fits. That's the trap. They stop being identical the day the content gets longer than the container.
What happens when the box doesn't fit?
I built a 400x300 container with overflow: auto and put one child in it, then ran 12 techniques against four child sizes: 200x100 (fits), 600x500 (too big both ways), 600x100 (too wide) and 200x500 (too tall). Playwright 1.62.1 drove Chromium 151.0.7922.34, Firefox 153.0 and WebKit 26.5 on an Apple M1. For each case the script reads the child's getBoundingClientRect(), scrolls to the far corner, and counts how many pixels of the child sit above or left of the scroll origin. Those pixels are the ones no scrollbar, wheel or keyboard can reach, because a scroll container never scrolls to a negative position.
Every technique ran twice, each in a fresh browser. Two complete runs of the script produced identical output, and it asserts all of it (85 checks), so a browser release that changes an answer fails the run. The CPU was shared with other jobs while I measured; layout isn't a timing, so that doesn't touch the numbers.
| Technique | Fits: centered? | 600x500: px lost top / left | 600x100: left | 200x500: top |
|---|---|---|---|---|
flex + align-items / justify-content: center | yes | 100 / 100 | 100 | 100 |
flex + safe center | yes | 0 / 0 | 0 | 0 |
flex + margin: auto on the child | yes | 0 / 0 | 0 | 0 |
grid + place-items: center (auto track) | yes | 0 / 0 | 0 | 0 |
grid + place-items: center (100% track) | yes | 100 / 100 | 100 | 100 |
grid + place-items: safe center (100% track) | yes | 0 / 0 | 0 | 0 |
grid + place-content: center | yes | 100 / 100 | 100 | 100 |
grid + margin: auto on the child | yes | 0 / 0 | 0 | 0 |
absolute + top/left: 50% + translate(-50%, -50%) | yes | 100 / 100 | 100 | 100 |
absolute + inset: 0 + margin: auto | yes | 100 / 0 | 0 | 100 |
absolute + inset: 0 + align-self / justify-self: center | yes | 0 / 0 | 0 | 0 |
text-align: center + line-height + inline-block | 0.3 to 1.1 px low | 0 / 0 | 0 | 0 |
Read the 100s as "100 px of my content is gone". The three engines agreed on every box position and every real scroll limit. Nothing was ever cut off at the bottom or right, so the loss is always at the top and left.
Plain flex centering is the unsafe one
display: flex with align-items: center and justify-content: center is the snippet in every tutorial, and it centers a 600x500 box in a 400x300 container by letting 100 px stick out on each side. The right and bottom halves scroll into view. The left and top halves don't, ever. A modal whose heading sits in that lost strip, or a login card on a short landscape phone, is a bug with no scrollbar to rescue it. The fix is one word: align-items: safe center loses nothing, and so does margin: auto on the child, which flexbox refuses to make negative.
place-items: center depends on the track, not the property
The same place-items: center lost nothing in one test and 100 px in another. On a default grid the auto track grows to the size of the box, so there's no free space to split and nothing to lose. Put a fixed track under it (grid-template: 100% / 100%, or any explicit track as big as the container) and you're back to unsafe centering, 100 px lost in all three engines.
place-items: center is safe or unsafe depending on the grid track it sits on, not on the property itself, so the same line can hide a clipped heading.
inset: 0 with margin: auto is safe in one direction only
The absolute-positioning trick doesn't behave the same on both axes. With inset: 0; margin: auto, a box that is too wide stays pinned to the left edge (0 px lost), but a box that is too tall loses 100 px at the top. The two auto-margin rules in CSS 2.1 differ: for the horizontal axis (section 10.3.7) negative margins get zeroed, and for the vertical axis (10.6.4) they don't. Chromium, Firefox and WebKit all implement that asymmetry the same way. The spec has said so for years; the table is what makes it visible.
The line-height trick is a pixel off
text-align: center with a line-height equal to the container height and an inline-block child is the old-school way. It centered the 200x100 box 0.81 px low in Chromium, 0.32 px in Firefox and 1.13 px in WebKit, because vertical-align: middle lines the box up with the parent's x-height midpoint instead of its own center. On a 16px font that's invisible. On a pixel-snapped icon it isn't.
Where the engines disagree
Only in one property. For plain flex centering and place-content: center, WebKit's scrollWidth counts the unreachable overflow (600 where Chromium and Firefox report 500), and for place-content its scrollHeight does too, even though the clamped maximum scroll is identical in all three. If your code computes a scroll limit as scrollWidth - clientWidth, WebKit hands you a number that's 100 too big.
The measurement has limits. It covers left-to-right text only (in RTL the lost side flips). It uses one font size. The headless engines took no scrollbar gutter, and the script asserts the client box stayed 400x300, but a classic always-visible scrollbar on another setup would shift every center by half its width. CSS anchor positioning isn't in the matrix. The script, the technique list and the raw assertions are in the css-centering-cross-engine example; npm test reprints the table on your machine.
What's the difference between centering text and centering a block?
People conflate these two constantly. text-align: center centers the inline content inside a box. It does nothing to the box itself.
<div class="banner">
<p>This text is centered by text-align.</p>
</div>
.banner {
text-align: center; /* moves the text, not the div */
width: 500px;
}
If you want the 500px banner itself in the middle of the page and the text inside it centered too, you need both margin: 0 auto on the box and text-align: center for the content. Miss that distinction and you'll swear the div "won't center" when it's doing exactly what you asked.
What are the common centering gotchas?
First: in normal flow, margin: auto never centers vertically, because the vertical auto margins resolve to zero. Inside a flex or grid container it does center vertically, and as the table shows it's one of the few techniques that also stays safe on overflow. Per the CSS specs, block-axis auto margins only distribute free space in flex, grid and absolutely positioned layout.
Second: the height: 100% trap. A percentage height only works if the parent has a defined height. Chain enough elements without one and your centered child silently collapses to content height.
/* This fails unless html and body also have height set */
.wrapper {
height: 100%;
display: grid;
place-items: center;
}
Reach for min-height: 100dvh on the outermost container instead. Which brings up the third gotcha: use 100dvh, not 100vh. The old vh unit ignores mobile browser toolbars, so a full-screen centered element gets clipped under the address bar on phones. The dynamic viewport unit dvh accounts for that chrome as it slides in and out.
Which method should you default to in 2026?
| Method | Centers | Reach for it when |
|---|---|---|
display: flex with margin: auto on the child | Both axes | Content length is unknown and must stay reachable |
display: flex with justify-content and align-items | Both axes | Content is known to fit; add safe if it might not |
display: grid with place-items: center | Both axes | The element stands alone and the track is auto-sized |
position: absolute with translate(-50%, -50%) | Both axes | Overlays where you don't own the parent and the box is small |
margin: 0 auto | Horizontal only | A block element that already has a width set |
text-align: center | Inline content only | Text and inline elements inside a block |
A quick way to pick without overthinking it:
- Check if the element is a flex or grid child already. If so, align it from the parent instead of adding new rules.
- Check if you control the parent's
displayproperty. If you can't change it, fall back to absolute positioning withtranslate(-50%, -50%). - Ask whether the content can ever outgrow the container. If it can, use
margin: autoon the child or thesafekeyword. - Check the horizontal-only case. A fixed-width block needs nothing more than
margin: 0 auto.
My default after this measurement: display: flex; justify-content: center; align-items: center is still fine for a hero with three lines of copy. For anything holding user-generated text, a translated label or a dialog body, I now put margin: auto on the child, because it centers the same way and can't clip. That's an opinion, and you can disagree with it, but the 100 px in the table is not an opinion.