Nearly every CSS centering method puts a small box in the right place. They differ when the content is bigger than its container, because some push part of it where scrolling can't reach. We measured both in three browser engines.
Try it in your browser
Pick a method and a child size. Then scroll the container and see whether both corners of the child can be reached.
Measuring
When the child fits
We put a 100 by 60 pixel child in a 400 by 300 pixel container and measured how far its center was from the container’s center, in pixels, across and then down.
| Method | Chrome | Firefox | WebKit |
|---|---|---|---|
| margin-inline: auto | 0, -120 | 0, -120 | 0, -120 |
| margin: auto on a block | 0, -120 | 0, -120 | 0, -120 |
| text-align: center | 0, -120 | 0, -120 | 0, -120 |
| justify-self: center on a block | 0, -120 | -150, -120 | -150, -120 |
| align-content: center and margin-inline: auto | 0, 0 | 0, 0 | 0, 0 |
| flex, center | 0, 0 | 0, 0 | 0, 0 |
| flex, margin: auto | 0, 0 | 0, 0 | 0, 0 |
| grid, place-items: center | 0, 0 | 0, 0 | 0, 0 |
| grid, place-content: center | 0, 0 | 0, 0 | 0, 0 |
| absolute, translate(-50%, -50%) | 0, 0 | 0, 0 | 0, 0 |
| absolute, inset: 0, margin: auto | 0, 0 | 0, 0 | 0, 0 |
| absolute, inset: 0, place-self: center | 0, 0 | 0, 0 | 0, 0 |
| table-cell, vertical-align: middle | 0, 0 | 0, 0 | 0, 0 |
| line-height equal to height | 0, 0.84 | 0, 1.35 | 0, 0.84 |
Every method meant for both directions came back as 0, 0 in all three browsers. So did a 101 by 61 box, which has an odd pixel of space to split.
margin: auto on a plain block only works sideways, and left the box 120 pixels above center. justify-self: center on a block centered it in Chrome and did nothing in Firefox or WebKit. The line-height trick missed by 0.84 pixels in Chrome and WebKit and 1.35 in Firefox, because vertical-align: middle aims at half the parent’s x-height above the baseline, a point slightly below the middle of the line.
When the child is too big
Next we put a 600 by 500 pixel child in the same container, set overflow: auto, and scrolled to each end to measure how much of the child stayed out of reach.
| Method | Offset | Cut off, Chrome and Firefox | Cut off, WebKit |
|---|---|---|---|
| flex, center | 0, 0 | 100 left, 100 top | 100 top |
| flex, safe center | 100, 100 | Nothing | Nothing |
| flex, margin: auto | 100, 100 | Nothing | Nothing |
| grid, place-items: center | 100, 100 | Nothing | Nothing |
| grid, minmax(0, 1fr) tracks | 0, 0 | 100 left, 100 top | 100 left, 100 top |
| grid, place-content: center | 0, 0 | 100 left, 100 top | Nothing |
| grid, place-content: safe center | 100, 100 | Nothing | Nothing |
| align-content: center on a block | 100, 100 | Nothing | Nothing |
| absolute, translate(-50%, -50%) | 0, 0 | 100 left, 100 top | 100 left, 100 top |
| absolute, inset: 0, margin: auto | 100, 0 | 100 top | 100 top |
| absolute, inset: 0, place-self: center | 100, 100 | Nothing | Nothing |
Flex with center, place-content, translate and a minmax(0, 1fr) grid all hold the child on exact center, so 100 pixels stick out past the top and left edges. Scrolling stops at the start of the container, and that part of the child is before the start.
place-items: center lost nothing because a grid’s default track is auto. The track grows to the size of the child, so the child starts at the top left. Fix the track size with minmax(0, 1fr) and it loses the same 100 pixels as flex.
margin: auto on a flex item and the safe keyword both center while there is room and go to the start edge when there isn’t. Nothing was lost with either.
WebKit kept more of the child in reach. The Box Alignment draft, read on 8 November 2026, says justify-content and align-content in a scroll container should “alter the scrollable overflow area” so overflow can be reached. WebKit did that, scrolling to minus 100 pixels, and Chrome and Firefox did not. The rule doesn’t cover align-items, so WebKit still lost the top of a flex child. align-content on a plain block left everything reachable in all three: Chrome and Firefox moved the child to the start, and WebKit kept it centered with scrolling to reach it.
An absolute box with inset: 0 and margin: auto lost its top but not its left. CSS 2 stops left and right auto margins from going negative and sets no such limit for top and bottom. place-self: center on the same box lost nothing.
translate has a second cost. With left: 50% the box has only half the container as available width, so a paragraph wrapped at 200 pixels and came out 144 tall. In a grid the same text used 400 pixels and was 72 tall.
Centering on the whole page
Here a 300 by 2000 pixel element sat in a window 800 pixels tall. All three browsers returned the same numbers, and a 200 pixel element came out exactly centered with every method.
| Method | Box height | Cut off above | Cut off below |
|---|---|---|---|
| body min-height: 100vh, flex | 2000 | 0 | 0 |
| body height: 100vh, flex | 2000 | 600 | 0 |
| body min-height: 100vh, grid | 2000 | 0 | 0 |
| body height: 100vh, grid | 2000 | 0 | 0 |
| fixed, translate(-50%, -50%) | 2000 | 600 | 600 |
| fixed, inset: 0, margin: auto | 2000 | 600 | 600 |
| popover | 2000 | 600 | 600 |
| dialog, showModal() | 762 | 0 | 0 |
Flex on a body with height: 100vh put the top 600 pixels above the window, where the page can’t scroll. With min-height: 100vh the body grows and nothing is lost. Grid was fine with either, thanks to the auto track.
A fixed element doesn’t scroll with the page, so anything taller than the window loses both ends. A popover did the same, because the browser’s own styles give it overflow: auto and no maximum height. A modal dialog held up: its default max-height kept it to 762 pixels and the content scrolled inside it. Give a popover a max-height yourself.
Photo by Cara DenisonDoes translate blur the box?
A 101 by 61 box needs a half pixel shift to sit in the middle. We drew it in black on white at 1x and read the pixels back. Flex, grid and margin: auto snapped it to whole pixels and drew 101 solid columns. With translate(-50%, -50%), Chrome and WebKit drew 100 solid columns and 2 grey ones, and the same at the top and bottom, so the box had soft edges. Firefox snapped it. The translate property matched transform. Text inside the box matched the flex version pixel for pixel in all three.
Text that looks off center
Centering works on the line box, and where the letters sit inside that box depends on the font. We centered a 64 pixel capital H with flex and found its ink in a screenshot. The table gives pixels from true center, taking the largest value across the three browsers. Positive means the letter sits low.
| Font | Normal line height | text-box: trim-both cap alphabetic |
|---|---|---|
| Arial | -1 | 0 |
| Georgia | 1 | 0 |
| Verdana | 2.5 | -0.5 |
| Times New Roman | -0.5 | -0.5 |
| Segoe UI | 4.5 | -0.5 |
| Trebuchet MS | 0 | 0 |
text-box: trim-both cap alphabetic brought every font to within half a pixel. MDN’s compatibility data lists it for Chrome 133, Safari 18.2 and Firefox 154. Browsers without it ignore the line and give the normal result.
What to use
/* One child, both directions */
.parent { display: grid; place-items: center; }
/* The whole page */
body { min-height: 100vh; display: grid; place-items: center; }
/* A block, horizontally */
.column { max-width: 40rem; margin-inline: auto; }
/* Vertically, without flex or grid */
.banner { align-content: center; }
/* Several items in a row */
.row { display: flex; justify-content: safe center; align-items: safe center; }
/* On top of other content */
.badge { position: absolute; inset: 0; place-self: center; }
/* Text in a button or label */
.button { text-box: trim-both cap alphabetic; }Use flex when there is more than one child, since a grid gives each child its own row. Use text-align: center for text and inline content. On an oversized inline-block it fell back to the start and lost nothing. For a modal, use dialog with showModal(). Keep translate for elements you know are small.
place-self on an absolute box is new. MDN’s data lists Chrome 122, Firefox 134 and Safari 26. To support older Safari, use inset: 0 with margin: auto and a fit-content width and height, and accept that a box taller than its container loses its top.
How this was tested
A script loaded each combination on 8 November 2026 on Windows 11, in a 1000 by 800 pixel window at 1x, and read positions with getBoundingClientRect. Scrollbars were hidden so the container measured 400 by 300 in every browser. Scroll limits came from setting the scroll position past each end. The child held a solid inner block so it had a real minimum size, as an image does. A flex item with a width and nothing solid inside shrank to 400 pixels and never overflowed.
- Chrome 156.0.8078.80, headless.
- Firefox 159.0.1, headless.
- WebKit 26.0 from Playwright 1.56.1, on Windows. This build uses Safari’s engine but it is not Safari, and nothing here was run on an iPhone.
Touch and keyboard scrolling were not part of the run, and neither were screens above 1x. The full output is available as raw JSON. The test script and this article were prepared with help from AI.
How this used to work
Before flexbox, vertical centering meant display: table-cell, a line-height equal to the box height, or a negative margin of half a known height. Table cells still center exactly, but they ignore overflow: our 400 by 300 cell grew to 600 by 500. align-content on a plain block is much newer. MDN’s data lists Chrome 123, Firefox 125 and Safari 17.4.
Common questions
Why is the top of my flex item cut off?
align-items: center keeps the item centered even when it is taller than the container, so its top goes past the edge where scrolling can’t follow. Use safe center or margin: auto on the item.
Should I center with flexbox or grid?
For one child, grid: place-items: center is one line after display: grid and it lost nothing when the child was too big. For a row of items, flex with safe center.
Does margin: auto center vertically?
Not on a plain block, where it stayed at the top. It does on a flex item, a grid item and an absolute box with inset: 0.
Sources
- CSS Box Alignment Level 3, Editor’s Draft: overflow alignment safety
- CSS 2.2: absolutely positioned widths and margins
- HTML Standard: default styles for dialog and popover
- MDN browser compatibility data: align-content
- MDN browser compatibility data: align-self
- MDN browser compatibility data: text-box-trim
