Centring a div both ways takes two lines on the parent: display: grid; place-items: center; or display: flex with justify-content: center and align-items: center. For horizontal only, give the div a width and margin-inline: auto. The parent needs a height taller than the child for vertical centring to show.
| Need | CSS on the parent | Tailwind |
|---|---|---|
| Both ways | display: grid; place-items: center | grid place-items-center |
| Both ways | display: flex; justify-content: center; align-items: center | flex items-center justify-center |
| Horizontal block | (on the child) margin-inline: auto + a width | mx-auto w-… |
| Over an image | position: relative, child inset: 0 + margin: auto | relative / absolute inset-0 m-auto |
Updated
Pick a method and an axis and the demo box moves, the CSS for the parent and child is written out, and a note says where that method breaks. Seven techniques are covered, including the old ones you will still meet in existing code, followed by a decision table for picking one.
| Axis | Parent | Child | Tailwind |
|---|---|---|---|
| horizontal | display: flex; justify-content: center; | (nothing) | flex justify-center |
| vertical | display: flex; align-items: center; height: 220px; | (nothing) | flex items-center h-[220px] |
| both | display: flex; justify-content: center; align-items: center; height: 220px; | (nothing) | flex items-center justify-center h-[220px] |
Vertical centring needs the parent to be taller than the child. With flex-direction: column the two properties swap axes.
| Axis | Parent | Child | Tailwind |
|---|---|---|---|
| horizontal | display: grid; justify-items: center; | (nothing) | grid justify-items-center |
| vertical | display: grid; align-items: center; height: 220px; | (nothing) | grid items-center h-[220px] |
| both | display: grid; place-items: center; height: 220px; | (nothing) | grid place-items-center h-[220px] |
Several children stack in rows, each centred in its own row, so they spread out vertically instead of sitting together. Wrap them in one element.
| Axis | Parent | Child | Tailwind |
|---|---|---|---|
| horizontal | (nothing) | width: 140px; margin-inline: auto; | w-[140px] mx-auto |
| vertical | Not possible | - | - |
| both | Not possible | - | - |
In normal block flow it only centres horizontally, and only when the child has a width smaller than the parent. Inside a flex or grid parent, margin: auto centres both ways.
| Axis | Parent | Child | Tailwind |
|---|---|---|---|
| horizontal | position: relative; | position: absolute; left: 50%; transform: translateX(-50%); | relative / absolute left-1/2 -translate-x-1/2 |
| vertical | position: relative; height: 220px; | position: absolute; top: 50%; transform: translateY(-50%); | relative h-[220px] / absolute top-1/2 -translate-y-1/2 |
| both | position: relative; height: 220px; | position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); | relative h-[220px] / absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 |
The child leaves the flow, so the parent no longer grows around it. Half-pixel offsets can blur text, and the transform makes the child a containing block and stacking context.
| Axis | Parent | Child | Tailwind |
|---|---|---|---|
| horizontal | position: relative; | position: absolute; inset-inline: 0; width: 140px; margin-inline: auto; | relative / absolute inset-x-0 w-[140px] mx-auto |
| vertical | position: relative; height: 220px; | position: absolute; inset-block: 0; height: 60px; margin-block: auto; | relative h-[220px] / absolute inset-y-0 h-[60px] my-auto |
| both | position: relative; height: 220px; | position: absolute; inset: 0; width: 140px; height: 60px; margin: auto; | relative h-[220px] / absolute inset-0 m-auto w-[140px] h-[60px] |
The child needs an explicit size on the centred axis. Without it, inset: 0 simply stretches the child to fill the parent. fit-content widths also work in current browsers.
| Axis | Parent | Child | Tailwind |
|---|---|---|---|
| horizontal | text-align: center; | display: inline-block; | text-center / inline-block |
| vertical | Not possible | - | - |
| both | Not possible | - | - |
Only moves inline content: text, images, inline-block boxes. A normal block div ignores it, and the text inside the child inherits the centring too.
| Axis | Parent | Child | Tailwind |
|---|---|---|---|
| horizontal | Not possible | - | - |
| vertical | height: 60px; line-height: 60px; | display: inline-block; line-height: normal; vertical-align: middle; | h-[60px] leading-[60px] / inline-block leading-normal align-middle |
| both | height: 60px; line-height: 60px; text-align: center; | display: inline-block; line-height: normal; vertical-align: middle; | h-[60px] leading-[60px] text-center / inline-block leading-normal align-middle |
Breaks as soon as the text wraps onto a second line, and the height has to be hard-coded twice. Fine for a one-line badge or button, nothing else.
| Method | Horizontal | Vertical | Both | Watch out for |
|---|---|---|---|---|
| Flexbox | Yes | Yes | Yes | Vertical centring needs the parent to be taller than the child. With flex-direction: column the two properties swap axes. |
| Grid place-items | Yes | Yes | Yes | Several children stack in rows, each centred in its own row, so they spread out vertically instead of sitting together. Wrap them in one element. |
| margin: auto | Yes | No | No | In normal block flow it only centres horizontally, and only when the child has a width smaller than the parent. Inside a flex or grid parent, margin: auto centres both ways. |
| Absolute + transform | Yes | Yes | Yes | The child leaves the flow, so the parent no longer grows around it. Half-pixel offsets can blur text, and the transform makes the child a containing block and stacking context. |
| Absolute inset + margin auto | Yes | Yes | Yes | The child needs an explicit size on the centred axis. Without it, inset: 0 simply stretches the child to fill the parent. fit-content widths also work in current browsers. |
| text-align: center | Yes | No | No | Only moves inline content: text, images, inline-block boxes. A normal block div ignores it, and the text inside the child inherits the centring too. |
| line-height | No | Yes | Yes | Breaks as soon as the text wraps onto a second line, and the height has to be hard-coded twice. Fine for a one-line badge or button, nothing else. |
In a real page use min-height: 100dvh on the wrapper. The frame here uses a fixed height so it fits.
<div style="min-height: 200px; display: grid; place-items: center; background: #f1f5f9">
<div style="padding: 16px 20px; background: #fff; border-radius: 12px; box-shadow: 0 4px 16px #0002">
Sign in to continue
</div>
</div>inset: 0 with margin: auto centres a fixed-size child without transform, so edges stay crisp.
<div style="position: relative; height: 170px; max-width: 300px; background: linear-gradient(135deg,#1e3a8a,#4F98FB); border-radius: 12px">
<button style="position: absolute; inset: 0; margin: auto; width: 56px; height: 56px; border-radius: 50%; border: 0; font-size: 20px">▶</button>
</div>Auto margins on a flex child take all spare space, so the logo sits in the middle of the bar while menu and cart stay at the sides.
<nav style="display: flex; align-items: center; gap: 8px; padding: 10px; background: #0f172a; color: #fff">
<span>Menu</span>
<strong style="margin-inline: auto">Bakehouse</strong>
<span>Cart</span>
</nav>
Horizontal centring is easy in block layout because width is known: the parent's width is fixed by its container, so auto margins can split the leftover space. Height is the opposite, it is decided by the content, which is why vertical centring needed tricks for years.
Flexbox and grid changed that by letting the parent distribute free space on both axes. Once the parent has a known height, alignment properties do the rest, and the child stays part of the normal flow.
Absolute positioning is right when the centred thing should float over other content, such as a play button over a video thumbnail or a spinner over a card. The inset plus margin auto version avoids the blurry half-pixel edges a translate can produce.
text-align is still the quickest way to centre an icon or inline button inside a line of text. line-height centring belongs only in one-line badges you control fully.
Usually because the parent has no extra height. A block is only as tall as its content, so there is no space to centre within. Give the parent a height, min-height (for example 100dvh for a full screen) or let it stretch inside a taller flex or grid layout.
display: grid; place-items: center; on the parent. It centres the child both ways, works for any child size and needs no width or height on the child.
Make body or a wrapper at least the viewport height: min-height: 100dvh; display: grid; place-items: center;. For a modal that floats above the page, use position: fixed; inset: 0; margin: auto; with a set width and height, or the native dialog element, which centres itself.
Yes, for horizontal centring of a block that has a width narrower than its parent. margin-inline: auto is the modern logical form and leaves the top and bottom margins untouched.
Put flex items-center justify-center, or grid place-items-center, on the parent and give it a height such as h-screen or min-h-dvh. For a horizontally centred block, add mx-auto and a width or max-width to the child.
Related: Flexbox and grid cheat sheet, CSS position guide, CSS box model, CSS text styling, CSS transforms.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.