CSS text-align: what it moves and what it does not

text-align decides where each line of content sits inside its box. It moves text, images and inline-block buttons. It never moves the box itself.

text-align sets where each line of inline content sits inside a block: to the left, right, center, or stretched to both edges. Inline content means text, images, and inline-block elements such as buttons.

A block with a width, like a <div>, does not move. To center that, use margin: 0 auto.

Try it. Press each value and watch which items move and which one stays.

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>What text-align moves</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
  .controls button { font: inherit; font-size: 14px; padding: 6px 11px; border: 1px solid #c9ced8; border-radius: 8px; background: #fff; cursor: pointer; }
  .controls button.on { background: #1d2330; color: #fff; border-color: #1d2330; }
  label { display: block; font-size: 14px; margin-bottom: 10px; }
  code { font-family: ui-monospace, Consolas, monospace; font-size: 13px; }

  /* the container: text-align is set here and inherited by its content */
  .box { background: #fff; border: 1px dashed #a8b0bd; border-radius: 10px; padding: 12px; }
  .box p { margin: 0 0 10px; line-height: 1.5; }
  .pill { display: inline-block; padding: 7px 14px; border-radius: 99px; background: #2563eb; color: #fff; border: 0; font: inherit; }
  .block { width: 150px; margin-top: 10px; padding: 8px; border-radius: 8px; background: #fde2da; color: #9a3412; font-size: 14px; }
  .block.auto { margin-left: auto; margin-right: auto; background: #d6f2df; color: #0f5132; }
</style>
</head>
<body>
<div class="controls" id="controls">
  <button class="on">left</button><button>center</button><button>right</button><button>justify</button><button>start</button><button>end</button>
</div>
<label><input type="checkbox" id="auto"> Center the block with <code>margin: 0 auto</code></label>

<div class="box" id="box">
  <p>This paragraph is inline text. Every line inside it follows text-align, so it moves when you press a button above.</p>
  <img alt="" width="96" height="40" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='40'%3E%3Crect width='96' height='40' rx='8' fill='%23f59e0b'/%3E%3C/svg%3E">
  <br>
  <button class="pill">inline-block</button>
  <div class="block" id="block">A 150px block. text-align does not move it.</div>
</div>

<script>
  const box = document.getElementById('box');
  const buttons = document.querySelectorAll('#controls button');

  buttons.forEach((btn) => btn.addEventListener('click', () => {
    box.style.textAlign = btn.textContent;  // e.g. "center"
    buttons.forEach((b) => b.classList.toggle('on', b === btn));
  }));

  // margin-left/right: auto is what centers a block with a width
  document.getElementById('auto').addEventListener('change', (e) => {
    document.getElementById('block').classList.toggle('auto', e.target.checked);
  });
</script>
</body>
</html>
One text-align on the dashed box. Text, the image and the button move. The 150px block does not, until you tick margin auto.
.box { text-align: center; }  /* on the parent, not on the items */

The six values

Value Lines sit Notes
start Where lines begin The initial value. Left in English, right with dir="rtl"
end Where lines end Right in English, left with dir="rtl"
left Left edge Same in every direction
right Right edge Same in every direction
center In the middle Each line is centered on its own
justify Both edges Spaces are stretched; the last line stays at start

text-align is inherited. Set it once on a section, and every paragraph, list item and table cell inside picks it up, unless something sets its own value.

It moves inline content, not the block

A block box fills its own row in the layout. It is not sitting on a line of text, so there is nothing for text-align to shift. The text inside the block does get centered, because the block inherits the value.

text-align centers the text, image and button. A block with a width needs automatic margins.
text-align centers the text, image and button. A block with a width needs automatic margins.

To center a block, give it a width and let the browser split the free space:

.card {
  width: 320px;      /* or max-width */
  margin: 0 auto;    /* equal space left and right */
}

If the parent is a flex container, text-align on it does not move the flex items either. They are blocks. Use justify-content: center on the container, covered in the flexbox guide. The display guide explains which elements are block and which are inline.

Centering images and buttons

An <img> is inline by default, and a <button> behaves like an inline-block. Both sit on a line, so text-align: center on the parent centers them. That is why one rule on a hero section centers the heading, the paragraph and the button together.

Two things break it. If the image has display: block (a common reset), text-align stops applying; use margin: 0 auto on the image.

If you put text-align on the image or the button itself, nothing happens, because the value is read from the element that holds the line.

Justify, gaps and hyphens

text-align: justify stretches the spaces in each line so both edges are straight. In a wide column the extra space is spread thin. In a narrow one, a line may hold two or three words, and the gaps between them grow wide.

The same sentence justified and left-aligned in a 170px column.
The same sentence justified and left-aligned in a 170px column.

Drag the width down and switch between left and justify. Then try the hyphens menu and the last-line option.

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>text-align: justify</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: grid; gap: 8px; margin-bottom: 12px; font-size: 14px; }
  .controls label { display: flex; align-items: center; gap: 8px; }
  input[type=range] { flex: 1; max-width: 260px; }
  select { font: inherit; }
  output { font-family: ui-monospace, Consolas, monospace; }

  .col {
    width: 240px; max-width: 100%;
    background: #fff; border-left: 3px solid #2563eb; border-right: 3px solid #2563eb;
    padding: 0 6px; font-size: 16px; line-height: 1.55;
  }
  .col p { margin: 0; padding: 8px 0; }
</style>
</head>
<body>
<div class="controls">
  <label>text-align
    <select id="align"><option>left</option><option selected>justify</option></select>
  </label>
  <label>hyphens
    <select id="hyph"><option>none</option><option selected>manual</option><option>auto</option></select>
  </label>
  <label><input type="checkbox" id="last"> text-align-last: justify</label>
  <label>width <input type="range" id="width" min="140" max="560" value="240"> <output id="out">240px</output></label>
</div>

<!-- lang="en" tells the browser which hyphenation rules to use -->
<div class="col" id="col" lang="en">
  <p>Justified text stretches the spaces in each line so both edges are straight. In a narrow column, a long word such as characteristically or incomprehensibility leaves a line with only two or three words, and the spaces between them grow very wide.</p>
  <p>The soft hyphens in <i>in&shy;ter&shy;na&shy;tion&shy;al&shy;iza&shy;tion</i> are break points you add by hand.</p>
</div>

<script>
  const col = document.getElementById('col');
  const $ = (id) => document.getElementById(id);

  $('align').addEventListener('change', (e) => col.style.textAlign = e.target.value);
  $('hyph').addEventListener('change', (e) => col.style.hyphens = e.target.value);
  $('last').addEventListener('change', (e) => col.style.textAlignLast = e.target.checked ? 'justify' : '');
  $('width').addEventListener('input', (e) => {
    col.style.width = e.target.value + 'px';
    $('out').textContent = e.target.value + 'px';
  });

  // start state matches the selects
  col.style.textAlign = 'justify';
  col.style.hyphens = 'manual';
</script>
</body>
</html>
Narrow the column to see the gaps grow. Soft hyphens split the long word only when hyphens is manual or auto.
  • hyphens: manual (the default) breaks words only at soft hyphens you add with &shy;. They do not depend on a dictionary.
  • hyphens: auto lets the browser hyphenate on its own. It needs a lang attribute on the element or page, and a hyphenation dictionary for that language. If the browser has none, the text breaks as before.
  • text-align-last: justify stretches the last line as well. Leave it off for paragraphs; it suits a single line such as a label row.

text-justify chooses how the space is added, for example inter-word or inter-character. Check how your target browsers handle it, and make sure the text still reads well without it.

Aligning table columns

Numbers are easier to compare when their last digits line up. Right-align numeric columns, and turn on tabular figures so each digit has the same width:

th, td { text-align: start; }
.num   { text-align: end; font-variant-numeric: tabular-nums; }

Header cells are centered by default in browsers, so set th explicitly or the heading will sit away from its column. For column widths, see table column width.

Right-to-left text: start and end

start and end follow the dir attribute. left and right never change. A layout written with start and end flips correctly when the same page is shown in Arabic or Hebrew.

start and end swap sides with dir="rtl". left and right stay put.
start and end swap sides with dir="rtl". left and right stay put.

The finished example below puts the pieces together: a centered hero, a pricing table with the prices aligned at the end, and a card you can switch between ltr and rtl.

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>text-align on a real page</title>
<style>
  body { margin: 0; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }

  /* 1. Centered hero: text, button and image are inline content */
  .hero { text-align: center; padding: 22px 16px; background: #fff; }
  .hero h1 { margin: 0 0 6px; font-size: 24px; }
  .hero p { max-width: 30em; margin: 0 auto 12px; color: #4b5563; } /* margin auto centers the block */
  .hero a { display: inline-block; padding: 9px 18px; border-radius: 8px; background: #2563eb; color: #fff; text-decoration: none; }

  /* 2. Pricing table: words at the start, numbers at the end */
  .wrap { padding: 14px 16px; }
  table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 10px; overflow: hidden; }
  th, td { padding: 8px 10px; border-bottom: 1px solid #e5e7eb; text-align: start; }
  th { background: #eef1f5; font-size: 13px; }
  .num { text-align: end; font-variant-numeric: tabular-nums; } /* digits line up */
  tfoot td { font-weight: 700; border-bottom: 0; }

  /* 3. The same card in both directions */
  .row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 14px; }
  .row button { font: inherit; padding: 5px 10px; border: 1px solid #c9ced8; border-radius: 8px; background: #fff; cursor: pointer; }
  .card { background: #fff; border-radius: 10px; padding: 10px 12px; }
  .card p { margin: 4px 0; padding: 4px 6px; border-radius: 6px; background: #f8fafc; font-size: 14px; }
  .s { text-align: start; } .e { text-align: end; } .l { text-align: left; }
</style>
</head>
<body>
<section class="hero">
  <h1>Team plan</h1>
  <p>Everything in the hero is centered by one text-align on the section.</p>
  <a href="#plans">See prices</a>
</section>

<div class="wrap" id="plans">
  <table>
    <thead><tr><th>Item</th><th class="num">Seats</th><th class="num">Price</th></tr></thead>
    <tbody>
      <tr><td>Starter</td><td class="num">1</td><td class="num">$9.00</td></tr>
      <tr><td>Team</td><td class="num">10</td><td class="num">$79.00</td></tr>
      <tr><td>Business</td><td class="num">100</td><td class="num">$690.00</td></tr>
    </tbody>
    <tfoot><tr><td>Total</td><td class="num">111</td><td class="num">$778.00</td></tr></tfoot>
  </table>
</div>

<div class="wrap">
  <div class="row"><span>Direction: <b id="dirName">ltr</b></span><button id="flip">Switch to rtl</button></div>
  <div class="card" id="card" dir="ltr">
    <p class="s">text-align: start</p>
    <p class="e">text-align: end</p>
    <p class="l">text-align: left (never flips)</p>
  </div>
</div>

<script>
  const card = document.getElementById('card');
  document.getElementById('flip').addEventListener('click', (e) => {
    const rtl = card.dir === 'ltr';
    card.dir = rtl ? 'rtl' : 'ltr';  // start/end follow dir, left/right do not
    document.getElementById('dirName').textContent = card.dir;
    e.target.textContent = 'Switch to ' + (rtl ? 'ltr' : 'rtl');
  });
</script>
</body>
</html>
A centered hero, a price table with numbers at the end, and start/end next to left in both directions.

When it does not work

What you see Cause Fix
A div with a width stays on the left text-align does not move blocks margin: 0 auto on the div, or flexbox on the parent
A full-width element never looks centered It is width: 100%, so there is no free space around it Give it a smaller width or max-width
An image will not center The image is display: block margin: 0 auto on the image
Nothing changes on a span or button text-align was set on the inline item Set it on the parent block
Big gaps between words justify in a narrow column Use start, widen the column, or add hyphens with lang
A form or list is centered by accident text-align: center inherited from a section above Set text-align: start on the part that should not be centered
Text sits on the wrong side in RTL left or right used instead of start/end Switch to start and end
Flex items do not move Flex items are blocks justify-content on the flex container

Long words and URLs that overflow a narrow column are a wrapping problem, not an alignment one. Word break in HTML covers it.

Alignment is easier to judge on the real page than in a screenshot, especially on a phone and in both directions. 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 press the buttons and flip the direction themselves. If you change the code later, the same link shows the new version.

Questions people ask

Why does text-align: center not center my div?

text-align positions the inline content inside a block: text, images, inline-block elements. A div is a block box, so it is not part of any line and does not move. Give the div a width and margin: 0 auto, or center it from its parent with flexbox.

What is the difference between text-align: left and text-align: start?

left always means the left edge. start means the edge where lines begin, which is the left in English and the right when dir="rtl". The initial value of text-align is start, so right-to-left pages line up correctly without extra CSS.

Why is the last line of justified text not stretched?

With text-align: justify, the last line of a paragraph (and any line before a forced break) keeps start alignment. Set text-align-last: justify if you want that line stretched too.

How do I center an image with text-align?

An img is inline by default, so text-align: center on its parent centers it. If the image has display: block, text-align no longer affects it; use margin: 0 auto on the image instead.

Does text-align work on a span?

Not on the span itself. text-align applies to block containers and is inherited by their content. Setting it on an inline span changes nothing; set it on the paragraph or div around the span.

Keep reading