Dotted pixel art of a coral and mustard sunset glowing through clouds above a dark sea.

Centering anything in CSS

Author
gunlaug team
Updated
8 Nov 2026
Reading time
7 min

Photo by kien virak

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.

Top leftBottom right

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.

Offset from true center of a box that fits, across then down, in pixels
MethodChromeFirefoxWebKit
margin-inline: auto0, -1200, -1200, -120
margin: auto on a block0, -1200, -1200, -120
text-align: center0, -1200, -1200, -120
justify-self: center on a block0, -120-150, -120-150, -120
align-content: center and margin-inline: auto0, 00, 00, 0
flex, center0, 00, 00, 0
flex, margin: auto0, 00, 00, 0
grid, place-items: center0, 00, 00, 0
grid, place-content: center0, 00, 00, 0
absolute, translate(-50%, -50%)0, 00, 00, 0
absolute, inset: 0, margin: auto0, 00, 00, 0
absolute, inset: 0, place-self: center0, 00, 00, 0
table-cell, vertical-align: middle0, 00, 00, 0
line-height equal to height0, 0.840, 1.350, 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.

A 600 by 500 child in a 400 by 300 scrolling container: offset and pixels out of reach
MethodOffsetCut off, Chrome and FirefoxCut off, WebKit
flex, center0, 0100 left, 100 top100 top
flex, safe center100, 100NothingNothing
flex, margin: auto100, 100NothingNothing
grid, place-items: center100, 100NothingNothing
grid, minmax(0, 1fr) tracks0, 0100 left, 100 top100 left, 100 top
grid, place-content: center0, 0100 left, 100 topNothing
grid, place-content: safe center100, 100NothingNothing
align-content: center on a block100, 100NothingNothing
absolute, translate(-50%, -50%)0, 0100 left, 100 top100 left, 100 top
absolute, inset: 0, margin: auto100, 0100 top100 top
absolute, inset: 0, place-self: center100, 100NothingNothing

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.

A 2000 pixel tall element in an 800 pixel window: box height and pixels out of reach
MethodBox heightCut off aboveCut off below
body min-height: 100vh, flex200000
body height: 100vh, flex20006000
body min-height: 100vh, grid200000
body height: 100vh, grid200000
fixed, translate(-50%, -50%)2000600600
fixed, inset: 0, margin: auto2000600600
popover2000600600
dialog, showModal()76200

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.

Dotted pixel art of a gold sunset behind a black treeline, reflected in amber water.Photo by Cara Denison

Does 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.

How far a centered capital H sat from true center, in pixels
FontNormal line heighttext-box: trim-both cap alphabetic
Arial-10
Georgia10
Verdana2.5-0.5
Times New Roman-0.5-0.5
Segoe UI4.5-0.5
Trebuchet MS00

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