How to center a div (and pick the right way)

Put display: grid and place-items: center on the parent, give the parent a height, and the div sits in the middle. The other methods exist for cases where that one does not fit.

To center a div in both directions, put two lines on its parent: display: grid and place-items: center. The parent also needs a height that is larger than the div. For horizontal centering only, margin: 0 auto on a div with a width is enough.

.parent {
  display: grid;
  place-items: center;
  min-height: 100dvh;  /* or any height larger than the child */
}

Try all six methods on the same child below. Pick a method, then drag the sliders to resize the parent. The CSS shown is exactly the CSS applied.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Six ways to center a div</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
  .tabs button {
    font: inherit; font-size: 13px; padding: 6px 10px; border-radius: 8px;
    border: 1px solid #c9cdd4; background: #fff; cursor: pointer;
  }
  .tabs button[aria-pressed="true"] { background: #1d4ed8; border-color: #1d4ed8; color: #fff; }
  .sizes { display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: 13px; margin-bottom: 10px; }
  .sizes input { width: 130px; vertical-align: middle; }
  /* the parent: dashed outline so you can see its edges */
  #parent { border: 2px dashed #9aa3b2; background: #fff; box-sizing: border-box; }
  /* the child: the same size in every method */
  #child {
    width: 120px; height: 64px; border-radius: 10px; box-sizing: border-box;
    background: #2563eb; color: #fff; font-size: 13px; padding: 8px;
  }
  pre {
    margin: 10px 0 0; padding: 10px 12px; border-radius: 8px; background: #1d2330; color: #e6e9ef;
    font: 13px/1.5 ui-monospace, Consolas, monospace; white-space: pre-wrap;
  }
  .note { font-size: 13px; color: #5b6270; margin: 8px 0 0; }
</style>
</head>
<body>
<div class="tabs" id="tabs"></div>
<div class="sizes">
  <label>Parent width <input type="range" id="w" min="160" max="600" value="340"></label>
  <label>Parent height <input type="range" id="h" min="100" max="220" value="170"></label>
</div>
<div id="parent"><div id="child">child</div></div>
<pre id="code"></pre>
<p class="note" id="note"></p>

<script>
  // each method: CSS for the parent, CSS for the child, and what it centers
  const methods = {
    'margin: auto': {
      parent: '',
      child: 'margin: 0 auto;',
      note: 'Horizontal only. Needs a width. The child stays at the top.'
    },
    'text-align': {
      parent: 'text-align: center;',
      child: 'display: inline-block;',
      note: 'Horizontal only. Centers inline content: text, inline-block boxes, images.'
    },
    'flex': {
      parent: 'display: flex;\n  justify-content: center;\n  align-items: center;',
      child: '',
      note: 'Both axes. Goes on the parent.'
    },
    'grid': {
      parent: 'display: grid;\n  place-items: center;',
      child: '',
      note: 'Both axes in two lines. Goes on the parent.'
    },
    'inset + margin': {
      parent: 'position: relative;',
      child: 'position: absolute;\n  inset: 0;\n  margin: auto;',
      note: 'Both axes. Needs a width and a height. The child leaves the layout.'
    },
    'translate': {
      parent: 'position: relative;',
      child: 'position: absolute;\n  top: 50%; left: 50%;\n  transform: translate(-50%, -50%);',
      note: 'Both axes. Works when the child size is unknown. The child leaves the layout.'
    }
  };

  const parent = document.getElementById('parent');
  const child = document.getElementById('child');
  const tabs = document.getElementById('tabs');
  const w = document.getElementById('w');
  const h = document.getElementById('h');

  function size() {
    parent.style.width = w.value + 'px';
    parent.style.maxWidth = '100%';
    parent.style.height = h.value + 'px';
  }

  function pick(name) {
    const m = methods[name];
    parent.style.cssText = m.parent;  // apply exactly the CSS that is shown
    child.style.cssText = m.child;
    size();
    document.getElementById('code').textContent =
      '.parent {' + (m.parent ? '\n  ' + m.parent : '') + '\n}\n' +
      '.child {\n  width: 120px;\n  height: 64px;' + (m.child ? '\n  ' + m.child : '') + '\n}';
    document.getElementById('note').textContent = m.note;
    tabs.querySelectorAll('button').forEach(b => b.setAttribute('aria-pressed', b.textContent === name));
  }

  Object.keys(methods).forEach(name => {
    const b = document.createElement('button');
    b.textContent = name;
    b.addEventListener('click', () => pick(name));
    tabs.appendChild(b);
  });
  w.addEventListener('input', size);
  h.addEventListener('input', size);
  pick('flex');
</script>
</body>
</html>
The same child, centered six ways. Resize the parent and watch which methods keep it in the middle.

Two of the six, margin: auto and text-align, only center left to right. The other four center both ways.

Which method to use

All six are correct CSS. They differ in what they need from the parent and child, and in whether the child stays in the normal page flow.

Start at the top and take the first question that matches.
Start at the top and take the first question that matches.
Method Axes Goes on Needs Child stays in the flow
margin: 0 auto Horizontal Child A width Yes
text-align: center Horizontal Parent Inline content Yes
Flex, both properties center Both Parent A parent height Yes
Grid, place-items: center Both Parent A parent height Yes
Absolute, inset: 0, margin: auto Both Child A width and height, a positioned parent No
Absolute, translate(-50%, -50%) Both Child A positioned parent No

A practical default: grid for a single child, flex when the parent already lines up several items, and absolute positioning only when the child must sit on top of other content.

Horizontal only: margin auto and text-align

margin: 0 auto sets the left and right margins to auto. The browser splits the leftover width equally between them. That only works when there is leftover width, so the block needs a width or max-width smaller than its parent.

.box {
  max-width: 480px;
  margin: 0 auto;
}

text-align: center works differently. It centers the inline content of a block: text, images, and elements with display: inline-block. It goes on the parent and is inherited, so text inside the child is centered too. The text-align guide covers every value.

Neither of these moves anything up or down. The CSS vertical-align property does not center a div either; it applies to inline content and table cells. CSS vertical-align explains where it does apply.

Both axes with flex or grid

Flex centering uses two properties on the parent. justify-content works along the main axis, left to right in a row, and align-items works across it.

.parent {
  display: flex;
  justify-content: center;
  align-items: center;
}

Grid does the same with place-items: center, a shorthand for align-items and justify-items. The flexbox and CSS grid guides go further, and justify-content shows each of its values live.

Both keep the child in the normal flow. Surrounding content still makes room for it, and it can grow with its text without overlapping anything.

Centering on top of content: absolute positioning

A badge over an image or a spinner over a card has to overlap other content. Here the child gets position: absolute, and the parent gets position: relative so the child is placed inside it. CSS position explains that pairing.

.parent { position: relative; }
.child {
  position: absolute;
  inset: 0;          /* top, right, bottom and left all 0 */
  margin: auto;
  width: 120px;
  height: 64px;
}

With all four edges at 0 and a fixed size, the auto margins share the leftover space evenly on every side. Without a width and height, the child stretches to fill the parent instead.

When the child's size is not known, use translate:

.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
top and left move the corner to the center. translate pulls the box back by half its own size.
top and left move the corner to the center. translate pulls the box back by half its own size.

Percentages in top and left refer to the parent. Percentages in translate() refer to the child. That difference is what puts the middle of the child on the middle of the parent.

Centering in the middle of the screen

A div in the middle of the page needs a parent as tall as the screen. Often that parent is body:

body {
  margin: 0;
  min-height: 100vh;   /* fallback */
  min-height: 100dvh;
  display: grid;
  place-items: center;
}

margin: 0 matters. Browsers give body a default margin, and 100vh plus that margin is taller than the screen, which adds a scrollbar.

On phones, the address bar appears and hides as you scroll. 100vh is sized for the screen with the bar hidden, so the box can be taller than what is visible and the center drops.

100dvh follows the visible viewport. Writing 100vh first means a browser that does not know dvh still gets a height.

Use min-height rather than height. If the content grows taller than the screen, the page scrolls instead of cutting it off. The page also needs the viewport meta tag to be sized correctly on a phone.

Why centering is not working

Most failures come from the parent, not the method. The three below are the ones that show up again and again.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Why centering fails</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
  .tabs button {
    font: inherit; font-size: 13px; padding: 6px 10px; border-radius: 8px;
    border: 1px solid #c9cdd4; background: #fff; cursor: pointer;
  }
  .tabs button[aria-pressed="true"] { background: #1d4ed8; border-color: #1d4ed8; color: #fff; }
  label { display: inline-block; font-size: 14px; margin-bottom: 10px; cursor: pointer; }
  .wrap { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
  code {
    display: block; padding: 8px 10px; border-radius: 8px; background: #1d2330; color: #e6e9ef;
    font: 13px/1.5 ui-monospace, Consolas, monospace; white-space: pre;
  }
  /* the grey area stands in for the space the parent sits in */
  .page { height: 170px; background: #e4e8ee; border-radius: 8px; }
  .parent { outline: 2px dashed #6b7280; outline-offset: -2px; background: #fff; }
  .child { background: #2563eb; color: #fff; font-size: 13px; padding: 8px; border-radius: 8px; box-sizing: border-box; }
  .note { font-size: 13px; color: #5b6270; margin: 10px 0 0; }

  /* case 1: flex centering, but the parent is only as tall as the child */
  .c1 .parent { display: flex; justify-content: center; align-items: center; }
  .c1.fixed .parent { height: 100%; }
  .c1 .child { width: 90px; }

  /* case 2: the child fills the parent, so there is no space to share */
  .c2 .parent { height: 100%; display: flex; justify-content: center; align-items: center; }
  .c2 .child { width: 100%; }
  .c2.fixed .child { width: 60%; }

  /* case 3: margin auto on a block that already fills the line */
  .c3 .parent { height: 100%; }
  .c3 .child { margin: 0 auto; }
  .c3.fixed .child { width: 120px; }
</style>
</head>
<body>
<div class="tabs" id="tabs">
  <button data-case="c1">1. Parent has no height</button>
  <button data-case="c2">2. Child is width: 100%</button>
  <button data-case="c3">3. margin: auto, no width</button>
</div>
<label><input type="checkbox" id="fix"> Apply the fix</label>
<div class="wrap">
  <code id="code"></code>
  <div class="page" id="demo"><div class="parent"><div class="child">child</div></div></div>
</div>
<p class="note" id="note"></p>

<script>
  // each case: the CSS before and after the fix, and what went wrong
  const cases = {
    c1: {
      css: ['.parent {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  /* height: auto */\n}',
            '.parent {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n}'],
      note: 'The dashed parent is only as tall as the child, so there is no space above or below.'
    },
    c2: {
      css: ['.child {\n  width: 100%;\n}', '.child {\n  width: 60%;\n}'],
      note: 'A child as wide as its parent has no space on either side to center in.'
    },
    c3: {
      css: ['.child {\n  margin: 0 auto;\n  /* width: auto */\n}', '.child {\n  margin: 0 auto;\n  width: 120px;\n}'],
      note: 'A block with no width fills the line, so the auto margins are zero.'
    }
  };

  const demo = document.getElementById('demo');
  const fix = document.getElementById('fix');
  let current = 'c1';

  function show() {
    demo.className = 'page ' + current + (fix.checked ? ' fixed' : '');
    document.getElementById('code').textContent = cases[current].css[fix.checked ? 1 : 0];
    document.getElementById('note').textContent = fix.checked ? 'Fixed: the child is centered.' : cases[current].note;
    document.querySelectorAll('#tabs button').forEach(b => b.setAttribute('aria-pressed', b.dataset.case === current));
  }

  document.querySelectorAll('#tabs button').forEach(b => b.addEventListener('click', () => {
    current = b.dataset.case;
    fix.checked = false;  // each case starts broken
    show();
  }));
  fix.addEventListener('change', show);
  show();
</script>
</body>
</html>
Three setups that look correct but do not center. Tick the fix to see what changes.

The first is the most common. A block's height is auto by default, meaning only as tall as its content. A parent that hugs its child has no empty space above or below, so vertical centering has nothing to do.

Vertical centering needs free space in the parent. Give the parent a height.
Vertical centering needs free space in the parent. Give the parent a height.

A related trap: height: 100% on a div only works if its own parent has a height. A percentage of an auto height is treated as auto. For full-screen centering, min-height: 100dvh avoids the whole chain.

A finished example: login card and modal

The same two lines center a whole page. The body below is a full-height grid with the card in its middle. Forgot password? opens a modal that uses the same trick on a fixed overlay.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Centered login card and modal</title>
<style>
  * { box-sizing: border-box; }
  body {
    margin: 0;                /* drop the default 8px, or 100vh overflows */
    min-height: 100vh;        /* fallback for older browsers */
    min-height: 100dvh;       /* follows the mobile address bar */
    display: grid;
    place-items: center;      /* the card sits in the middle of the screen */
    padding: 16px;
    font-family: system-ui, sans-serif;
    background: linear-gradient(135deg, #dbeafe, #f5f3ff);
  }
  .card {
    width: 100%; max-width: 320px;   /* narrower than the screen, so there is room to center */
    background: #fff; border-radius: 14px; padding: 22px;
    box-shadow: 0 10px 30px rgba(30, 41, 59, .15);
  }
  .card h1 { margin: 0 0 6px; font-size: 20px; text-align: center; }
  .card label { display: block; font-size: 13px; margin: 10px 0 4px; }
  .card input { width: 100%; padding: 9px 10px; border: 1px solid #c9cdd4; border-radius: 8px; font: inherit; }
  .card button { width: 100%; margin-top: 12px; padding: 10px; border: 0; border-radius: 8px; font: inherit; cursor: pointer; }
  .primary { background: #1d4ed8; color: #fff; }
  .link { background: none; color: #1d4ed8; }
  #out { font-size: 13px; color: #0f5132; min-height: 1.3em; margin: 6px 0 0; text-align: center; }

  /* the modal: a full-screen layer that centers its box */
  .overlay {
    position: fixed; inset: 0;
    display: none; place-items: center;
    padding: 16px; background: rgba(15, 23, 42, .55);
  }
  .overlay.open { display: grid; }
  .modal { width: 100%; max-width: 300px; background: #fff; border-radius: 12px; padding: 20px; }
  .modal h2 { margin: 0 0 8px; font-size: 17px; }
  .modal p { margin: 0 0 14px; font-size: 14px; color: #475569; }
  .modal button { padding: 8px 14px; border: 0; border-radius: 8px; background: #1d4ed8; color: #fff; font: inherit; cursor: pointer; }
</style>
</head>
<body>
<form class="card" id="form">
  <h1>Sign in</h1>
  <label for="email">Email</label>
  <input id="email" name="email" type="email" value="sam@example.com" required>
  <label for="pw">Password</label>
  <input id="pw" name="password" type="password" value="secret123" required>
  <button class="primary" type="submit">Sign in</button>
  <button class="link" type="button" id="help">Forgot password?</button>
  <p id="out"></p>
</form>

<div class="overlay" id="overlay">
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="mt">
    <h2 id="mt">Reset your password</h2>
    <p>A reset link would go to your email. The overlay's grid keeps this box in the middle.</p>
    <button type="button" id="close">Close</button>
  </div>
</div>

<script>
  const form = document.getElementById('form');
  const overlay = document.getElementById('overlay');

  // demo only: show what would be sent instead of sending it
  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const data = new FormData(form);
    document.getElementById('out').textContent =
      'Would send ' + data.get('email') + ' and a ' + data.get('password').length + '-character password';
  });

  const open = () => overlay.classList.add('open');
  const close = () => overlay.classList.remove('open');
  document.getElementById('help').addEventListener('click', open);
  document.getElementById('close').addEventListener('click', close);
  overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });  // click the dark area
  document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
</script>
</body>
</html>
The body centers the card. The fixed overlay centers the modal. Sign in shows what the form would send.
  • The card: width: 100% with max-width: 320px, so it fills a small phone but leaves space to center on a wide screen.
  • The overlay: position: fixed; inset: 0 covers the screen, and display: grid; place-items: center puts the box in its middle.
  • Padding on the parent keeps the card and modal off the screen edges on a phone.

For more on the modal itself, including the built-in <dialog> element, see HTML CSS modal.

When it does not work

What you see Cause Fix
Centered left to right, stuck at the top The parent's height is auto Give the parent a height or min-height
height: 100% on the parent changes nothing Its own parent has no height Use min-height: 100dvh, or set heights up the chain
The child fills the row and looks left-aligned The child is width: 100% Use a smaller width or a max-width
margin: 0 auto does nothing The block has no width, or it is inline Set a width, or display: block on an image or span
Flex properties change nothing display: flex is on the child Put flex, and the centering properties, on the parent
The box jumps off-center on hover A hover transform replaced the translate Repeat the translate in the hover transform, or use grid
Absolute child fills the whole parent inset: 0 with no width or height Give the child a size, or use translate
Absolute child centers on the page, not the parent No positioned parent Add position: relative to the parent
A scrollbar appears with 100vh body's default margin body { margin: 0; }
On a phone the box sits too low 100vh counts the hidden address bar Add min-height: 100dvh after 100vh

The hover case is easy to miss. transform is a single property, so transform: scale(1.05) on hover replaces translate(-50%, -50%) completely. Write both in one value:

.child:hover { transform: translate(-50%, -50%) scale(1.05); }

The CSS transform guide covers combining functions.

Centering is easiest to judge on the real screen, especially on a phone with its address bar. A screenshot shows one size only, and an .html attachment may open as plain code on a phone.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can open the modal and resize the window themselves. If you change the code later, the same link shows the new version.

Questions people ask

What is the shortest way to center a div horizontally and vertically?

Put display: grid and place-items: center on the parent. The parent also needs a height larger than the child, for example min-height: 100dvh when you want the div in the middle of the screen.

Why does margin: 0 auto not center my div?

A block element with no width stretches to fill its parent, so there is no space left for the auto margins to share. Give it a width or max-width smaller than the parent. margin: 0 auto also does nothing on an inline element such as a span.

Why does my div center horizontally but not vertically?

The parent has no height. A block's height is auto by default, which means only as tall as its content, so there is no empty space above or below to center in. Give the parent a height or min-height.

Should I use flex or grid to center a div?

Both work. Grid does it in two lines with place-items: center. Flex needs justify-content and align-items, and is the natural choice when the parent already lays out a row of items with flex.

Why use 100dvh instead of 100vh?

On mobile browsers the address bar shows and hides as you scroll, and 100vh is sized for the viewport with the bar hidden. A box that is 100vh tall can then be taller than the visible screen, so its center sits too low. 100dvh follows the viewport as it currently is.

Keep reading