CSS vertical-align: what middle really does, and how to center in a box

vertical-align lines up an icon, an image or a badge with the text on its line. It does not center the contents of a div. That job belongs to flex or grid.

vertical-align: middle only works on inline-level boxes (text, <img>, <svg>, inline-block) and on table cells. On an inline box it moves that box up or down within its line of text.

On a table cell it moves the cell's content. On a normal <div> it does nothing at all.

So the answer depends on what you want. To line up an icon with the words beside it, vertical-align is the right tool. To center something inside a box, use display: flex with align-items: center, or display: grid with place-items: center.

Try every value first. The same value is applied to an icon, an image and an inline-block that sit on one line of text.

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>vertical-align playground</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .values { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
  .values button {
    font: 600 13px ui-monospace, Consolas, monospace; padding: 6px 9px;
    border: 1px solid #cfd4dc; border-radius: 7px; background: #fff; cursor: pointer;
  }
  .values button.on { background: #1d4ed8; border-color: #1d4ed8; color: #fff; }
  .line {
    position: relative; background: #fff; border-radius: 10px;
    padding: 18px 14px; font-size: 30px; line-height: 2.2;
  }
  /* the three inline-level things we align */
  .icon { width: 1em; height: 1em; }
  .pic  { width: 44px; height: 44px; }
  .box  { display: inline-block; padding: 2px 8px; font-size: 14px; line-height: 1.4;
          background: #fde68a; border-radius: 6px; }
  /* red line = baseline of the text, blue line = baseline + half the x-height */
  .guide { position: absolute; left: 0; right: 0; height: 0; border-top: 1px dashed; pointer-events: none; }
  .base { border-color: #dc2626; }
  .mid  { border-color: #2563eb; }
  .probe { display: inline-block; width: 0; height: 0; }   /* its bottom sits on the baseline */
  .ex { display: inline-block; width: 0; height: 1ex; }    /* measures the x-height */
  .key { font-size: 13px; margin-top: 10px; color: #4b5563; }
  .key b { font-weight: 600; }
</style>
</head>
<body>
<div class="values" id="values">
  <button class="on">baseline</button><button>middle</button><button>top</button>
  <button>bottom</button><button>text-top</button><button>text-bottom</button>
  <button>super</button><button>sub</button><button>-4px</button>
</div>

<div class="line" id="line">Text
  <svg class="icon a" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="#16a34a"/><path d="M7 12l3 3 7-7" stroke="#fff" stroke-width="2.5" fill="none"/></svg>
  xg <img class="pic a" alt="" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 44'%3E%3Crect width='44' height='44' rx='8' fill='%23f97316'/%3E%3C/svg%3E">
  <span class="box a">inline-block</span><span class="probe" id="probe"></span><span class="ex" id="ex"></span>
  <div class="guide base" id="base"></div><div class="guide mid" id="mid"></div>
</div>
<p class="key"><b style="color:#dc2626">Red</b>: baseline. <b style="color:#2563eb">Blue</b>: baseline + half the x-height, where <code>middle</code> lines up.</p>

<script>
  const line = document.getElementById('line');
  const probe = document.getElementById('probe');
  const ex = document.getElementById('ex');

  function drawGuides() {
    const top = line.getBoundingClientRect().top;
    const baseline = probe.getBoundingClientRect().bottom - top;
    const xHeight = ex.getBoundingClientRect().height;
    document.getElementById('base').style.top = baseline + 'px';
    document.getElementById('mid').style.top = (baseline - xHeight / 2) + 'px';
  }

  document.getElementById('values').addEventListener('click', (e) => {
    if (e.target.tagName !== 'BUTTON') return;
    document.querySelectorAll('.values button').forEach((b) => b.classList.toggle('on', b === e.target));
    // apply the chosen value to the icon, the image and the inline-block
    document.querySelectorAll('.a').forEach((el) => { el.style.verticalAlign = e.target.textContent; });
    drawGuides();
  });

  addEventListener('resize', drawGuides);
  drawGuides();
</script>
</body>
</html>
Pick a value. The red line is the baseline, the blue line is where middle aims.

What vertical-align actually moves

Every line of text has a baseline: the line that letters such as H and x sit on.

By default, every inline box on the line puts its own baseline on that line. For an image or an SVG that has no text, the bottom edge goes on the baseline.

vertical-align changes that for the element it is set on. It is not inherited, and it does not reach into the element's children. That is why putting it on a parent does nothing to the items inside.

On a block it is ignored. On inline boxes it moves the box itself; on table cells it moves the content.
On a block it is ignored. On inline boxes it moves the box itself; on table cells it moves the content.

The values, and the lines they line up with

The values fall into two groups. Some are measured from the parent's text, and two are measured from the whole line box.

Each value lines the element up with a different line. middle aims at half the x-height above the baseline.
Each value lines the element up with a different line. middle aims at half the x-height above the baseline.
Value Lines up
baseline The element's baseline with the parent's baseline (the default)
middle The element's middle with the parent's baseline plus half its x-height
text-top The element's top with the top of the parent's font
text-bottom The element's bottom with the bottom of the parent's font
top The element's top with the top of the whole line
bottom The element's bottom with the bottom of the whole line
sub / super The baseline with the parent's subscript or superscript position
4px, -0.2em, 20% Raised by that amount above the baseline (negative lowers it; % is of line-height)

The x-height is the height of a lowercase x in the font. Because middle aims at half of it, the middle of an icon set to middle lines up with the middle of the lowercase letters.

Next to capital letters it can look a pixel or two low.

When middle looks slightly off, a length gives exact control. vertical-align: -0.125em lowers an icon by an eighth of the font size and scales with it. SVG icons in HTML uses that value for icons sized in em.

Icons and badges next to text

Two common cases need different fixes.

  • An icon inside a sentence. Give the icon vertical-align: middle or a small negative length. The icon stays part of the text, so it wraps with it.
  • A button with an icon and a label. Make the button display: inline-flex with align-items: center. Flex centers the icon and the label against each other, whatever their sizes.
.icon { width: 1em; height: 1em; vertical-align: -0.125em; }

.btn { display: inline-flex; align-items: center; gap: 8px; }

Mixed font sizes on one line make baseline alignment look uneven, because a large word raises the height of the whole line. A badge next to a heading is a typical case.

Set vertical-align: middle on the small badge, or put both in a flex row.

The gap under an image

An <img> is an inline element, so it sits on the baseline like a letter. The line keeps room below the baseline for the tails of g, p and y. That room shows up as a thin strip under the image, inside its container.

The strip under an image is the space kept for letter tails. Take the image off the line and it goes away.
The strip under an image is the space kept for letter tails. Take the image off the line and it goes away.
img { display: block; }         /* image is no longer on a line of text */
/* or keep it inline: */
img { vertical-align: middle; } /* bottom also works */

Table cells: the one place it moves content

On a <td> or <th>, or anything with display: table-cell, vertical-align controls where the content sits inside the cell. Only baseline, top, middle and bottom do something different there; the other values act like baseline.

td { vertical-align: top; }     /* long cells read better from the top */
td.avatar { vertical-align: middle; }

How to center something vertically in a box

For the contents of a <div>, change the container's layout mode. Compare the four boxes below; only the first one fails.

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>vertical-align: middle does nothing on a div</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; }
  figure { margin: 0; }
  figcaption { font: 600 12.5px ui-monospace, Consolas, monospace; margin-bottom: 6px; }
  .frame { height: 130px; width: 100%; box-sizing: border-box; background: #fff;
           border: 1px dashed #aab1bd; border-radius: 10px; }
  .child { padding: 8px 12px; background: #2563eb; color: #fff; border-radius: 8px; font-size: 14px; }

  /* 1. does nothing: vertical-align is ignored on a block box */
  .v-align { vertical-align: middle; }

  /* 2. flex: align-items centers the child on the cross axis */
  .flex { display: flex; align-items: center; justify-content: center; }

  /* 3. grid: place-items sets both axes at once */
  .grid-center { display: grid; place-items: center; }

  /* 4. table-cell: here vertical-align does apply */
  .table { display: table; width: 100%; }  /* a table-cell needs a table around it to stretch */
  .cell { display: table-cell; vertical-align: middle; text-align: center; }
  .cell .child { display: inline-block; }

  .bad figcaption { color: #9a3412; }
  .good figcaption { color: #0f5132; }
</style>
</head>
<body>
<div class="grid">
  <figure class="bad">
    <figcaption>vertical-align: middle</figcaption>
    <div class="frame v-align"><div class="child">Stuck at top</div></div>
  </figure>
  <figure class="good">
    <figcaption>display: flex</figcaption>
    <div class="frame flex"><div class="child">Centered</div></div>
  </figure>
  <figure class="good">
    <figcaption>display: grid</figcaption>
    <div class="frame grid-center"><div class="child">Centered</div></div>
  </figure>
  <figure class="good">
    <figcaption>display: table-cell</figcaption>
    <div class="table"><div class="frame cell"><div class="child">Centered</div></div></div>
  </figure>
</div>
</body>
</html>
The same child in four boxes. vertical-align on a plain div does nothing; flex, grid and table-cell all center it.
  1. Flex. display: flex; align-items: center; centers the children across the row. Add justify-content: center to center them along it too. Flexbox covers the rest.
  2. Grid. display: grid; place-items: center; centers each child both ways in one line. See CSS grid.
  3. One line of text. Set line-height equal to the box height. It fails as soon as the text wraps; line-height in CSS shows why.
  4. Table cell. display: table-cell; vertical-align: middle; works, but flex and grid do the same with fewer side effects.
.box { display: flex; align-items: center; justify-content: center; }

.box { display: grid; place-items: center; }

The box needs a height taller than its content, from height, min-height or the layout around it. With no spare height there is nothing to center within. Which display value to reach for in general is covered in CSS display.

A finished example

A centered hero, a badge beside a heading, a button with an icon, and a table whose cells center their content.

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>Vertical alignment, finished</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }

  /* hero: grid centers the content box both ways */
  .hero { display: grid; place-items: center; min-height: 170px; border-radius: 12px;
          background: linear-gradient(135deg, #1e3a8a, #0f766e); color: #fff; text-align: center; }
  .hero h2 { margin: 0 0 12px; font-size: 22px; }

  /* heading with a badge: vertical-align: middle on the inline badge */
  .badge { display: inline-block; vertical-align: middle; font-size: 12px; font-weight: 700;
           padding: 3px 8px; border-radius: 99px; background: #fbbf24; color: #1d2330; }

  /* button with icon: inline-flex centers icon and label */
  .btn { display: inline-flex; align-items: center; gap: 8px; padding: 10px 16px;
         border: 0; border-radius: 9px; background: #fff; color: #1d2330; font: 600 15px system-ui, sans-serif; cursor: pointer; }
  .btn svg { width: 18px; height: 18px; flex: none; }

  /* table: every cell middle-aligned */
  table { width: 100%; border-collapse: collapse; margin-top: 14px; background: #fff; border-radius: 10px; overflow: hidden; }
  td, th { vertical-align: middle; padding: 8px 10px; border-bottom: 1px solid #e5e7eb; text-align: left; font-size: 14px; }
  th { background: #f9fafb; font-size: 12px; text-transform: uppercase; color: #6b7280; }
  td img { display: block; width: 36px; height: 36px; border-radius: 50%; }   /* no gap under the image */
  #out { margin-top: 10px; font-size: 13px; color: #4b5563; min-height: 1.2em; }
</style>
</head>
<body>
<section class="hero">
  <div>
    <h2>Release notes <span class="badge">NEW</span></h2>
    <button class="btn" id="btn" type="button">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12m0 0l-5-5m5 5l5-5M4 21h16" stroke="currentColor" stroke-width="2.2" fill="none" stroke-linecap="round"/></svg>
      Download
    </button>
  </div>
</section>

<table>
  <tr><th></th><th>Name</th><th>Notes</th></tr>
  <tr>
    <td><img alt="" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36 36'%3E%3Crect width='36' height='36' fill='%2360a5fa'/%3E%3C/svg%3E"></td>
    <td>Ana</td>
    <td>A long note that wraps onto two or three lines, while the name and picture stay centered next to it.</td>
  </tr>
  <tr>
    <td><img alt="" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36 36'%3E%3Crect width='36' height='36' fill='%2334d399'/%3E%3C/svg%3E"></td>
    <td>Jun</td>
    <td>Short.</td>
  </tr>
</table>
<div id="out"></div>

<script>
  let n = 0;
  document.getElementById('btn').addEventListener('click', () => {
    n += 1;
    document.getElementById('out').textContent = 'Download clicked ' + n + ' time' + (n > 1 ? 's' : '') + '.';
  });
</script>
</body>
</html>
Grid for the hero, vertical-align for the badge, inline-flex for the button, and middle-aligned table cells.

When it does not work

What you see Cause Fix
vertical-align: middle on a div changes nothing It does not apply to block boxes display: flex; align-items: center or display: grid; place-items: center
Set on the parent, the children do not move It is not inherited and only moves the element itself Put it on the icon or image, or use flex on the parent
An icon set to middle looks a little low middle aims at half the x-height, not the center of capitals Use a length such as -0.125em, or inline-flex
A thin gap under an image The image sits on the baseline, above the space for letter tails display: block or vertical-align: bottom on the img
Items on a line look uneven Mixed font sizes change the line height vertical-align: middle on the small item, or a flex row
It does nothing inside a flex container Flex items ignore vertical-align align-items on the container or align-self on the item
middle centers inside a td but not inside a div Table cells are the only boxes whose content it moves Use display: table-cell, or better, flex or grid

Alignment is easier to judge in a live page than in a screenshot. Sent as a link, the page runs its scripts, so the person you send it to can click through the values and resize the window themselves.

Paste the page into a NOS document and choose Create share link. HTML to link walks through it. If you change the CSS later, the same link shows the new version.

Questions people ask

Why is vertical-align: middle not working on my div?

A div is a block box, and vertical-align only applies to inline-level boxes (inline, inline-block, images) and table cells. On a block it is ignored. To center the div's children vertically, give the div display: flex; align-items: center, or display: grid; place-items: center.

How do I center text vertically in a div?

Use display: flex and align-items: center on the div, or display: grid with place-items: center. For a single line of text in a box with a fixed height, setting line-height equal to the height also works, but it breaks as soon as the text wraps.

How do I line up an icon with text?

If the icon sits inside a line of text, give it vertical-align: middle, or a small negative length such as -0.125em, and adjust by eye. If the icon and label form a button or a row, make the container display: inline-flex with align-items: center instead.

Why is there a small gap under my image?

An img is inline and sits on the text baseline, so the space reserved below the baseline for letters such as g and y shows under it. Set display: block on the image, or vertical-align: middle or bottom.

Does vertical-align work in a flex container?

No. Children of a flex container are flex items, and vertical-align has no effect on them. Use align-items on the container or align-self on one item.

Keep reading