The sup and sub tags: superscript and subscript in HTML

Wrap text in <sup> to raise it and in <sub> to lower it. Both shrink the text, and both can push their line away from its neighbours unless you add four lines of CSS.

Put text in <sup> to make it superscript (raised, as in x2) and in <sub> to make it subscript (lowered, as in H2O). Both are inline tags, like <b> or <a>:

<p>The area is x<sup>2</sup>. Water is H<sub>2</sub>O.</p>

The browser's default styles are font-size: smaller plus vertical-align: super for sup and vertical-align: sub for sub. They work, with one side effect: lines that contain them sit further from their neighbours. Tick the box to see the fix.

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>sup and sub push lines apart</title>
<style>
  body { margin: 0; padding: 18px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .box { max-width: 520px; margin: 0 auto; background: #fff; border-radius: 12px; padding: 16px 18px; box-shadow: 0 4px 16px rgba(0, 0, 0, .08); }
  p.text {
    margin: 0; font-size: 16px; line-height: 1.5;  /* 24px per line */
    /* guide lines every 24px: text sits on them when spacing is even */
    background: repeating-linear-gradient(transparent 0 23px, #f3b7a3 23px 24px);
  }
  /* the fix: sup and sub stop adding height to their line */
  .fixed sup, .fixed sub {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }
  .fixed sup { top: -0.5em; }
  .fixed sub { bottom: -0.25em; }
  .fixed p.text { background: repeating-linear-gradient(transparent 0 23px, #9fd8b2 23px 24px); }
  label { display: flex; gap: 8px; align-items: center; margin: 14px 0 8px; font-weight: 600; cursor: pointer; }
  input { width: 18px; height: 18px; }
  #out { font: 13px/1.5 ui-monospace, Consolas, monospace; background: #f4f5f7; border-radius: 8px; padding: 8px 10px; }
</style>
</head>
<body>
<div class="box" id="box">
  <p class="text" id="text">The area of a square is x<sup>2</sup> and a cube holds x<sup>3</sup>. Water is H<sub>2</sub>O and the air we breathe out carries CO<sub>2</sub>. She finished 1<sup>st</sup> in the race, and the claim needs a source.<sup>1</sup> Lines without sup or sub keep the normal gap; the others are pushed down.</p>
  <label><input type="checkbox" id="fix"> Apply the fix</label>
  <div id="out"></div>
</div>

<script>
  const text = document.getElementById('text');
  const out = document.getElementById('out');

  // count lines: text directly inside the paragraph sits at one top per line
  function countLines() {
    const tops = new Set();
    for (const node of text.childNodes) {
      if (node.nodeType !== 3) continue;
      const r = document.createRange();
      r.selectNodeContents(node);
      for (const rect of r.getClientRects()) tops.add(Math.round(rect.top));
    }
    return tops.size;
  }

  function report() {
    const h = text.getBoundingClientRect().height;
    const lines = countLines();
    out.textContent = 'Paragraph height: ' + h.toFixed(1) + 'px\n'
      + 'Even spacing: ' + lines + ' lines x 24px = ' + lines * 24 + 'px';
    out.style.whiteSpace = 'pre-line';
  }

  document.getElementById('fix').addEventListener('change', (e) => {
    document.getElementById('box').classList.toggle('fixed', e.target.checked);
    report();
  });
  addEventListener('resize', report);
  report();
</script>
</body>
</html>
The pink guides are 24px apart. Lines with sup or sub drift off them until you apply the fix.

Why sup and sub push lines apart

Each line of text is a box as tall as its line height.

The default super shift lifts the small text, and its own box, which still has a line height, pokes out above the top of that line. The browser grows the line to fit it. sub does the same below.

Measured in Chromium: with default styles the second line starts 3.3px late and the paragraph is 7px taller.
Measured in Chromium: with default styles the second line starts 3.3px late and the paragraph is 7px taller.

A few pixels on one line is easy to miss in a single paragraph. On a long page full of footnote numbers, every marked line is a little taller, and the text looks unevenly spaced.

The fix: four CSS declarations

The same rules ship in normalize.css. They set the size yourself and move the raise from vertical-align to position: relative, which does not affect layout:

sup, sub {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}
sup { top: -0.5em; }
sub { bottom: -0.25em; }
What each declaration does on its own.
What each declaration does on its own.

line-height: 0 is the part that stops the growth. position: relative with top moves only where the text is drawn, so the line keeps its height. Adjust -0.5em and -0.25em to suit your font.

For more on how the line box is measured, see line height in CSS, and for the other values of the property, CSS vertical-align.

What sup and sub are for

The HTML standard allows these tags only for typographical conventions with a specific meaning. Remove the tag and the meaning changes: x2 is not x2. The common cases:

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>sup and sub examples</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  /* even line spacing for every example */
  sup, sub { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
  sup { top: -0.5em; }
  sub { bottom: -0.25em; }
  .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
  .card { background: #fff; border-radius: 10px; padding: 8px 10px; box-shadow: 0 2px 8px rgba(0, 0, 0, .07); }
  .card h3 { margin: 0 0 4px; font-size: 12px; text-transform: uppercase; letter-spacing: .4px; color: #6b7280; }
  .show { font-size: 20px; line-height: 1.5; }
  code { display: block; margin-top: 6px; font: 12px/1.4 ui-monospace, Consolas, monospace; color: #374151; background: #f4f5f7; border-radius: 6px; padding: 5px 7px; white-space: pre-wrap; word-break: break-word; }
  button { margin: 12px 0 8px; font: 600 14px system-ui, sans-serif; padding: 9px 14px; border: 0; border-radius: 8px; background: #1d4ed8; color: #fff; cursor: pointer; }
  #plain { font: 13px/1.6 ui-monospace, Consolas, monospace; background: #fff; border: 1px dashed #c3c9d4; border-radius: 8px; padding: 8px 10px; }
  #plain:empty { display: none; }
</style>
</head>
<body>
<div class="grid">
  <div class="card"><h3>Exponent</h3>
    <div class="show">E = mc<sup>2</sup></div>
    <code>E = mc&lt;sup&gt;2&lt;/sup&gt;</code></div>
  <div class="card"><h3>Chemical formula</h3>
    <div class="show">H<sub>2</sub>SO<sub>4</sub></div>
    <code>H&lt;sub&gt;2&lt;/sub&gt;SO&lt;sub&gt;4&lt;/sub&gt;</code></div>
  <div class="card"><h3>Ion charge</h3>
    <div class="show">SO<sub>4</sub><sup>2&minus;</sup></div>
    <code>SO&lt;sub&gt;4&lt;/sub&gt;&lt;sup&gt;2&amp;minus;&lt;/sup&gt;</code></div>
  <div class="card"><h3>Ordinal</h3>
    <div class="show">1<sup>st</sup>, 2<sup>nd</sup>, 3<sup>rd</sup></div>
    <code>1&lt;sup&gt;st&lt;/sup&gt;</code></div>
  <div class="card"><h3>Trademark: character</h3>
    <div class="show">Acme&trade;</div>
    <code>Acme&amp;trade;</code></div>
  <div class="card"><h3>Trademark: sup</h3>
    <div class="show">Acme<sup>TM</sup></div>
    <code>Acme&lt;sup&gt;TM&lt;/sup&gt;</code></div>
  <div class="card"><h3>Footnote marker</h3>
    <div class="show">Sales rose.<sup><a href="#">1</a></sup></div>
    <code>&lt;sup&gt;&lt;a href="#note1"&gt;1&lt;/a&gt;&lt;/sup&gt;</code></div>
</div>

<button id="copy">Show the plain text</button>
<div id="plain"></div>

<script>
  // innerText is roughly what a plain-text field receives when you paste
  document.getElementById('copy').addEventListener('click', () => {
    const lines = [...document.querySelectorAll('.show')].map((el) => el.innerText);
    document.getElementById('plain').textContent = lines.join('  |  ');
  });
</script>
</body>
</html>
Math, chemistry, ordinals, trademarks and footnote markers, all with even line spacing. The button shows what is left as plain text.
Use Markup Result
Exponent mc<sup>2</sup> mc2
Chemical formula CO<sub>2</sub> CO2
Ion charge SO<sub>4</sub><sup>2&minus;</sup> SO42−
Ordinal 1<sup>st</sup> 1st
Footnote marker <sup><a href="#note1">1</a></sup> a raised, linked 1

Writing ordinals as plain 1st is equally correct. The superscript version is a style choice, so pick one and use it throughout.

Trademark, squared and other symbols as characters

Some raised symbols exist as their own characters: ™ (&trade;), ® (&reg;), ² (&sup2;) and ³ (&sup3;). A character is part of the text itself, so it stays raised when copied into a plain-text field, a chat or a spreadsheet cell.

A <sup> is formatting, and a plain-text paste turns x2 into x2.

Use the character when the text will be copied around, and <sup> when you want CSS control over size and position. The HTML entities list has the codes.

Don't use sup for looks

A small raised "New" or "Beta" next to a menu item is decoration, not a typographic convention. Use a <span class="badge"> and style it. The span can be any size, colour or shape, and it does not claim a meaning it does not have.

A footnote is a pair of links. The number in the text links to the note, and the note links back to the number. Each end needs an id:

<p>Tuesday suited everyone<sup><a id="ref1" href="#note1">1</a></sup>.</p>

<ol>
  <li id="note1">Monday had the weekly report.
    <a href="#ref1" aria-label="Back to reference 1">&uarr; back</a></li>
</ol>
The number and the note point at each other. scroll-margin-top keeps both ends out from under a sticky header.
The number and the note point at each other. scroll-margin-top keeps both ends out from under a sticky header.

With a sticky header, a jump puts the target at the very top of the window, under the header. scroll-margin-top on the targets makes the browser stop short.

Linking within a page is covered in more depth in link to an anchor on the same page.

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>Footnotes with sup</title>
<style>
  body { margin: 0; font: 16px/1.6 Georgia, serif; color: #1d2330; background: #fff; }
  header { position: sticky; top: 0; z-index: 1; height: 48px; display: flex; align-items: center; padding: 0 18px;
           background: #1d2330; color: #fff; font: 600 15px system-ui, sans-serif; }
  main { max-width: 560px; margin: 0 auto; padding: 8px 18px 40px; }
  /* even line spacing */
  sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; top: -0.5em; }
  sup a { text-decoration: none; padding: 0 2px; font-family: system-ui, sans-serif; font-weight: 600; }
  /* land below the 48px sticky header, not under it */
  [id] { scroll-margin-top: 60px; }
  /* highlight whatever the last link jumped to */
  :target { background: #fff3c4; border-radius: 4px; }
  .notes { border-top: 1px solid #d5d9e0; margin-top: 28px; padding-top: 6px; font-size: 14px; }
  .notes h2 { font: 700 14px system-ui, sans-serif; margin: 10px 0 4px; }
  .notes ol { padding-left: 22px; margin: 0; }
  .notes li { margin: 6px 0; }
  .back { text-decoration: none; font-family: system-ui, sans-serif; white-space: nowrap; }
  .filler { color: #6b7280; }
</style>
</head>
<body>
<header>Team handbook</header>
<main>
  <h1 style="font-size:22px;margin:14px 0 8px">Why the team meets on Tuesdays</h1>
  <p>Tuesday was the only day everyone was in the office<sup><a id="ref1" href="#note1" aria-describedby="notes-title">1</a></sup> when the meeting started.</p>
  <p class="filler">Scroll on, or tap a small number to jump to its note. Each note has a back link that returns you to the exact spot you left.</p>
  <p>The notes from each meeting go out the same afternoon.<sup><a id="ref2" href="#note2" aria-describedby="notes-title">2</a></sup> Nobody has to wait until Friday.</p>
  <p class="filler">The header stays on top while you scroll. Without scroll-margin-top, a jump would hide the target line under it.</p>
  <p>The day may change when the new schedule starts.<sup><a id="ref3" href="#note3" aria-describedby="notes-title">3</a></sup></p>

  <section class="notes">
    <h2 id="notes-title">Notes</h2>
    <ol>
      <li id="note1">Monday was ruled out because of the weekly report. <a class="back" href="#ref1" aria-label="Back to reference 1">&#8593; back</a></li>
      <li id="note2">Notes are shared as a link, not an attachment. <a class="back" href="#ref2" aria-label="Back to reference 2">&#8593; back</a></li>
      <li id="note3">The new schedule has not been decided yet. <a class="back" href="#ref3" aria-label="Back to reference 3">&#8593; back</a></li>
    </ol>
  </section>
  <p class="filler" style="height:380px">End of article.</p>
</main>
</body>
</html>
A finished article: numbered footnotes, back links, a sticky header, and :target highlighting where you land.

The <ol> numbers the notes for you. If you also want the markers in the text numbered automatically, CSS counters can do it:

article { counter-reset: fn; }
a.fn { counter-increment: fn; }
a.fn::after { content: counter(fn); }

The numbers then exist only in CSS. If the stylesheet does not load, the markers are empty, so for short pages typing the numbers is the safer choice.

When to use MathML instead

sup and sub handle one exponent or one index. Fractions, square roots, matrices and stacked limits need real math layout. For those, use the MathML <math> element, which browsers render directly without a script.

When it does not work

What you see Cause Fix
Lines with sup or sub sit further apart Default vertical-align: super / sub grows the line box line-height: 0, vertical-align: baseline, then position: relative with top / bottom
Superscript is too big or too small Default font-size: smaller, or a sup inside a sup shrinks again Set font-size: 75% (or your own value) on sup and sub
A footnote link lands under the sticky header The jump aligns the target with the top of the window scroll-margin-top on the targets, a bit more than the header height
A badge made with sup reads oddly or is hard to style sup is meant for typographic meaning, not decoration Use a <span> with a class
Pasted text shows x2 instead of x² Plain-text paste drops <sup> formatting Use a character such as &sup2; or &trade; where one exists

Footnotes are easiest to check by clicking them. A screenshot shows the numbers but not the jumps, and an .html attachment may not open at all on a phone.

To send the working page, paste it 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 click every footnote themselves.

If you change the code later, the same link shows the new version.

Questions people ask

What is the difference between the sup and sub tags?

sup raises text above the baseline, as in x² or 1st. sub lowers it below the baseline, as in H₂O. By default both use a smaller font size, set with font-size: smaller.

Why does sup make my line spacing uneven?

The default vertical-align: super lifts the small text, and its box can reach above the top of the line. The browser then makes that line taller. Setting line-height: 0 and vertical-align: baseline on sup and sub, and raising them with position: relative and top, keeps every line the same height.

Can I use sup just to make text small and raised, like a "New" badge?

The HTML standard says sup and sub are for typographical conventions with a specific meaning, not for looks alone. For a badge, use a span with a class and style it with CSS.

Should I write ™ or <sup>TM</sup>?

Both show a raised TM. The ™ character (&trade;) is a single character, so it stays raised when the text is pasted as plain text. <sup>TM</sup> gives you control over its size and position with CSS, but a plain-text paste turns it into ordinary TM.

When should I use MathML instead of sup and sub?

For a single exponent or index, sup and sub are enough. For fractions, roots, matrices or stacked limits, use the MathML <math> element, which lays out formulas the way math is typeset.

Keep reading