The HTML span tag: styling part of a line

A span is an empty wrapper that sits inside a line of text. It means nothing on its own, which is exactly why it is useful: it gives CSS and JavaScript a handle on a few words.

The <span> tag wraps part of a line so you can style it or reach it from JavaScript. It has no meaning and no default look. Put it around the words, give it a class, and style the class:

<p>Your order ships on <span class="day">Tuesday</span>.</p>

<style>
  .day { color: #b45309; font-weight: 600; }
</style>

Try the difference between a span and a div below. Tick the boxes and watch which one takes the new size.

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>span vs div</title>
<style>
  body { margin: 0; padding: 14px; font: 15px/1.6 system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-bottom: 12px; font-size: 13px; }
  .controls label { display: flex; align-items: center; gap: 5px; }
  .panes { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
  .pane { background: #fff; border-radius: 10px; padding: 10px 12px; }
  .pane h3 { margin: 0 0 6px; font-size: 13px; color: #5b6270; }
  .box { background: #fde68a; outline: 2px solid #d97706; }
  .size { font: 12px ui-monospace, Consolas, monospace; color: #5b6270; margin-top: 8px; }
  /* The toggles add these classes to both boxes */
  .w .box { width: 160px; }
  .h .box { height: 60px; }
  .pad .box { padding: 14px; }
  .mar .box { margin: 14px; }
  .ib .box { display: inline-block; }
  @media (max-width: 480px) { .panes { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<div class="controls" id="controls">
  <label><input type="checkbox" value="w"> width: 160px</label>
  <label><input type="checkbox" value="h"> height: 60px</label>
  <label><input type="checkbox" value="pad"> padding: 14px</label>
  <label><input type="checkbox" value="mar"> margin: 14px</label>
  <label><input type="checkbox" value="ib"> display: inline-block</label>
</div>

<div class="panes" id="panes">
  <div class="pane">
    <h3>&lt;span&gt; (inline)</h3>
    <p>Text before the <span class="box" id="s">span box</span> and text after it, wrapping onto the next line like any word.</p>
    <div class="size" id="sSize"></div>
  </div>
  <div class="pane">
    <h3>&lt;div&gt; (block)</h3>
    <p>Text before the</p>
    <div class="box" id="d">div box</div>
    <p>and text after it, on its own line.</p>
    <div class="size" id="dSize"></div>
  </div>
</div>

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

  // Show the size the browser actually drew
  function measure() {
    for (const id of ['s', 'd']) {
      const r = document.getElementById(id).getBoundingClientRect();
      document.getElementById(id + 'Size').textContent =
        'drawn: ' + Math.round(r.width) + ' x ' + Math.round(r.height) + ' px';
    }
  }

  document.getElementById('controls').addEventListener('change', (e) => {
    panes.classList.toggle(e.target.value, e.target.checked);
    measure();
  });
  window.addEventListener('resize', measure);
  measure();
</script>
</body>
</html>
The same styles on a span and a div. The span ignores width and height until it becomes inline-block.

span vs div: inline and block

A span and a div are both empty wrappers. The only difference is how they sit on the page.

A span is inline: it flows inside a line of text, and it can wrap onto the next line like any word. A div is block: it starts on a new line and stretches to the full width of its parent.

A span stays inside the sentence. A div breaks the sentence and takes a whole line.
A span stays inside the sentence. A div breaks the sentence and takes a whole line.
<span> <div>
Default display inline block
Width As wide as its content As wide as its parent
Line breaks None Before and after
Takes width and height No Yes
Can contain Text and inline elements Almost anything
Use it for Part of a sentence A section or a box

The rule of thumb: if it is part of a sentence, reach for a span. If it is a piece of the layout, reach for a div. CSS display covers every display value in depth.

Why width and height do nothing on a span

width and height do not apply to inline elements. The browser sizes a span from its text, so the first demo's span stays the same size when you tick them.

Padding and margin half work. Horizontal padding and margin push the neighbouring words apart. Vertical padding is painted, but the line does not grow to make room, so the background spills over the lines above and below. Vertical margin does nothing visible.

Left: an inline span ignores its size and its padding overlaps other lines. Right: inline-block stays in the line and makes room.
Left: an inline span ignores its size and its padding overlaps other lines. Right: inline-block stays in the line and makes room.

The fix is display: inline-block. The span stays in the line of text, but it now behaves like a small box: width, height and vertical spacing all apply.

The lines move apart to fit it. Badges, pills and small icons inside a sentence are often built this way.

.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 99px;
  background: #dbeafe;
}

Use a meaningful tag when one fits

A span tells the browser nothing about its text. HTML has inline tags that do say something, and they come with a default look. If one of them describes your words, use it instead of a span with a class.

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>span vs semantic inline tags</title>
<style id="page-css">
  body { margin: 0; padding: 14px; font: 15px/1.6 system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .card { background: #fff; border-radius: 10px; padding: 10px 14px; margin-bottom: 10px; }
  .card h3 { margin: 0 0 4px; font-size: 13px; color: #5b6270; }
  .card p { margin: 0; }
  /* Class-based styling for the span version */
  .bold { font-weight: 700; }
  .italic { font-style: italic; }
  .hl { background: #fde68a; }
  .mono { font-family: ui-monospace, Consolas, monospace; }
  table { border-collapse: collapse; width: 100%; font-size: 13px; background: #fff; border-radius: 10px; }
  th, td { text-align: left; padding: 5px 8px; border-bottom: 1px solid #eceef2; vertical-align: top; }
  button { font: inherit; font-size: 13px; padding: 6px 12px; border-radius: 8px; border: 1px solid #c9cdd4; background: #fff; cursor: pointer; margin-bottom: 10px; }
</style>
</head>
<body>
<button id="toggle" type="button">Turn off the page CSS</button>

<div class="card">
  <h3>With span + classes</h3>
  <p id="spanned"><span class="bold">Do not</span> reply <span class="italic">after</span> <span>Friday</span>: the <span class="hl">deadline</span> is <span>12 May</span>. Use code <span class="mono">SPRING</span> in the <span>FAQ</span> form.</p>
</div>

<div class="card">
  <h3>With semantic tags</h3>
  <p id="semantic"><strong>Do not</strong> reply <em>after</em> Friday: the <mark>deadline</mark> is <time datetime="2027-05-12">12 May</time>. Use code <code>SPRING</code> in the <abbr title="Frequently asked questions">FAQ</abbr> form.</p>
</div>

<table>
  <tr><th>Tag</th><th>Means</th><th>Browser default look</th></tr>
  <tr><td>&lt;strong&gt;</td><td>Important</td><td>Bold</td></tr>
  <tr><td>&lt;em&gt;</td><td>Stressed word</td><td>Italic</td></tr>
  <tr><td>&lt;mark&gt;</td><td>Highlighted for reference</td><td>Yellow background</td></tr>
  <tr><td>&lt;code&gt;</td><td>Computer code</td><td>Monospace</td></tr>
  <tr><td>&lt;time&gt;</td><td>A date or time (datetime attribute)</td><td>None</td></tr>
  <tr><td>&lt;abbr&gt;</td><td>Abbreviation (title = full form)</td><td>Dotted underline with a title</td></tr>
  <tr><td>&lt;span&gt;</td><td>Nothing</td><td>None</td></tr>
</table>

<script>
  // Removing the page's stylesheet leaves only the browser defaults
  const css = document.getElementById('page-css');
  const btn = document.getElementById('toggle');
  btn.addEventListener('click', () => {
    css.disabled = !css.disabled;
    btn.textContent = css.disabled ? 'Turn the page CSS back on' : 'Turn off the page CSS';
  });
</script>
</body>
</html>
The same sentence twice. Turn off the page CSS: the spans go plain, the semantic tags keep their meaning and default look.
Tag Means Default look
<strong> Important Bold
<em> Stressed, as you would say it Italic
<mark> Highlighted for reference Yellow background
<code> A piece of computer code Monospace
<time datetime="..."> A date or time, in a machine-readable form None
<abbr title="..."> An abbreviation, with the full form in title Dotted underline when it has a title
<span> Nothing None

You can restyle all of them. A <mark> does not have to be yellow and <strong> can be a colour instead of bold. The tag carries the meaning; CSS decides the look. Semantic HTML goes through the same idea for whole pages.

Work down the questions. A span is the right answer only when nothing above fits.
Work down the questions. A span is the right answer only when nothing above fits.

A span is still the right tag in two common cases. The first is pure decoration: a coloured word in a heading, or a price split into a large number and a small currency sign.

The second is a language change, such as <span lang="fr">déjà vu</span>, so browsers and screen readers know which language those words are in.

Styling part of a line

Colouring a single word is a common reason to type <span>. Put a class on the span and write the rule once in your stylesheet. For the colour values themselves, see HTML font color.

<h1>Plans from <span class="price">$9</span> a month</h1>

<style>
  .price { color: #047857; }
</style>

A style attribute on the span also works, and it is fine for a one-off. Once the same look appears twice, a class is easier to change later. Inline CSS covers when each is right.

A span picks up its parent's text styles: font, size, colour and line height. You only need to set what should differ from the text around it.

Spans in JavaScript: counters and wrapped words

Because a span changes nothing on its own, it is a clean place to put text that JavaScript updates. A counter is a span with an id, and the script only changes its textContent:

<p>You have <span id="count">0</span> items in your list.</p>

<script>
  document.getElementById('count').textContent = 3;
</script>

The other pattern is to wrap every word in its own span, so each word can be coloured, counted or animated separately. The finished example below does that, then lets buttons choose a range of words and colour them.

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 highlighter with span</title>
<style>
  body { margin: 0; padding: 14px; font: 15px/1.7 system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  #text { background: #fff; border-radius: 10px; padding: 12px 14px; margin: 0 0 12px; }
  .word { border-radius: 3px; padding: 1px 0; }
  .word.sel { outline: 2px solid #2563eb; }
  .word[data-color="yellow"] { background: #fde68a; }
  .word[data-color="green"] { background: #bbf7d0; }
  .word[data-color="pink"] { background: #fbcfe8; }
  .row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 8px; font-size: 13px; }
  .row b { min-width: 42px; }
  button { font: inherit; font-size: 13px; padding: 5px 10px; border-radius: 8px; border: 1px solid #c9cdd4; background: #fff; cursor: pointer; }
  button[data-color="yellow"] { background: #fde68a; }
  button[data-color="green"] { background: #bbf7d0; }
  button[data-color="pink"] { background: #fbcfe8; }
  .stats { font-size: 13px; background: #fff; border-radius: 10px; padding: 8px 14px; }
  .stats span { font-weight: 700; }
</style>
</head>
<body>
<p id="text">The span element has no meaning of its own. It groups a few words so that CSS can style them and JavaScript can find them. Wrap each word in a span and every word becomes something you can colour, count or move.</p>

<div class="row">
  <b>Start</b>
  <button type="button" data-move="start" data-by="-1">&larr;</button>
  <button type="button" data-move="start" data-by="1">&rarr;</button>
  <b>End</b>
  <button type="button" data-move="end" data-by="-1">&larr;</button>
  <button type="button" data-move="end" data-by="1">&rarr;</button>
</div>
<div class="row">
  <b>Colour</b>
  <button type="button" data-color="yellow">Yellow</button>
  <button type="button" data-color="green">Green</button>
  <button type="button" data-color="pink">Pink</button>
  <button type="button" data-color="">Clear</button>
</div>

<div class="stats">
  Selected: <span id="selWords">0</span> words, <span id="selChars">0</span> characters<br>
  Highlighted: <span id="hlWords">0</span> of <span id="allWords">0</span> words,
  <span id="hlChars">0</span> of <span id="allChars">0</span> characters (no spaces)
</div>

<script>
  // 1. Wrap every word of the paragraph in its own span
  const text = document.getElementById('text');
  const words = text.textContent.trim().split(/\s+/).map((w) => {
    const s = document.createElement('span');
    s.className = 'word';
    s.textContent = w; // textContent, so the text is never parsed as HTML
    return s;
  });
  text.replaceChildren(...words.flatMap((s) => [s, ' ']).slice(0, -1));

  let start = 0, end = 2;
  const $ = (id) => document.getElementById(id);
  const chars = (list) => list.reduce((n, w) => n + w.textContent.length, 0);

  // 2. Redraw the selection outline and the counters
  function update() {
    const picked = words.slice(start, end + 1);
    const lit = words.filter((w) => w.dataset.color);
    words.forEach((w, i) => w.classList.toggle('sel', i >= start && i <= end));
    $('selWords').textContent = picked.length;
    $('selChars').textContent = chars(picked);
    $('hlWords').textContent = lit.length;
    $('hlChars').textContent = chars(lit);
    $('allWords').textContent = words.length;
    $('allChars').textContent = chars(words);
  }

  // 3. Arrow buttons move the start or end of the range
  document.querySelectorAll('[data-move]').forEach((b) => b.addEventListener('click', () => {
    const by = Number(b.dataset.by), last = words.length - 1;
    if (b.dataset.move === 'start') start = Math.min(Math.max(start + by, 0), end);
    else end = Math.max(Math.min(end + by, last), start);
    update();
  }));

  // 4. Colour buttons set data-color on the selected spans; CSS does the rest
  document.querySelectorAll('button[data-color]').forEach((b) => b.addEventListener('click', () => {
    words.slice(start, end + 1).forEach((w) => {
      if (b.dataset.color) w.dataset.color = b.dataset.color;
      else delete w.dataset.color;
    });
    update();
  }));

  update();
</script>
</body>
</html>
Move the start and end with the arrow buttons, then pick a colour. The counters are spans that the script rewrites.

A few details make the code safe to reuse:

  • Build spans with createElement and textContent. Joining words into an innerHTML string would parse any < in the text as HTML.
  • Store state in data-* attributes. data-color="yellow" on the span, and one CSS rule per colour. The script never sets a colour directly.
  • Find spans with a class. querySelectorAll('.word') returns them in page order. querySelector covers the selector syntax.

The controls are real <button> elements, not spans. That matters: a span with a click listener cannot be reached with Tab and does not react to Enter or Space.

If clicking it does something, use a button. HTML button not clickable covers the opposite problem.

Do not put block elements inside a span

A span may only contain phrasing content: text and inline elements such as a, strong, em, img and other spans. Putting a <div>, <p> or <ul> inside a span is invalid HTML.

Browsers still show something, which is why the mistake survives.

If the span sits inside a paragraph, the <div> start tag closes the paragraph early, so the div and the text after it end up outside it. Your styles for that paragraph then stop applying halfway through.

If the wrapper needs to hold blocks, make the wrapper a div. If the inner part should stay in the line, make it a span too.

When it does not work

What you see Cause Fix
width or height on the span does nothing Inline elements ignore both display: inline-block or block
The span's background overlaps the lines above and below Vertical padding on an inline element does not grow the line display: inline-block, or increase line-height on the parent
A paragraph's styles stop halfway, or an empty paragraph appears A block element inside a span inside a p Use a div wrapper, or only inline content inside
A clickable span cannot be reached with Tab A span is not focusable or keyboard-operable Use a <button>, or <a href> for navigation
The span's class colour is ignored A more specific rule wins, such as #intro span over .hl Make your selector as specific, for example #intro .hl
The styled span spills onto two lines Inline elements wrap with the text white-space: nowrap on the span, or make it inline-block

The specificity case is easy to miss. An id in a selector outweighs any number of classes:

#intro span { color: gray; }  /* id + element: wins */
.hl { color: red; }           /* one class: loses */
#intro .hl { color: red; }    /* id + class: now wins */

A page of highlighted words, badges and live counters is easier to show than to describe. A screenshot freezes the counters, 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 click the buttons and watch the counters change. If you change the code later, the same link shows the new version.

Questions people ask

What does the span tag do in HTML?

Nothing visible by itself. A span is a generic inline container: it wraps some text or inline elements so you can style them with a class or find them with JavaScript. It adds no meaning, no line break and no default style.

What is the difference between span and div?

A span is inline: it sits inside a line of text and is as wide as its content. A div is block: it starts on a new line and is as wide as its parent. Use span for part of a sentence and div for a section of the page.

Why does width not work on my span?

Width and height do not apply to inline elements such as span. Give the span display: inline-block if it should stay in the line, or display: block if it should take its own line.

Is span still valid in HTML5?

Yes. span is part of the current HTML standard. It accepts only the global attributes, such as class, id, style, title, lang and data-* attributes.

Can I put a div inside a span?

No. A span may only contain phrasing content: text and inline elements such as a, strong or img. If a block element is needed inside, make the outer element a div instead.

Keep reading