The section tag in HTML: section vs article vs div

A section is one themed part of a page or post, and it comes with a heading. If the part stands on its own, it is an article. If you only need a box for CSS, it is a div.

Use <section> for one themed part of a bigger whole that has its own heading: the Features and Pricing parts of a page, or the chapters of a long post.

Use <article> when the content makes sense on its own, such as a post, a card or a comment. Use <div> when the wrapper is only there for CSS.

The three look identical on screen. What changes is what the browser tells assistive technology. Switch the blocks below and watch the lists on the right.

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>section, article or div: structure viewer</title>
<style>
  body { margin: 0; padding: 12px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .wrap { display: grid; grid-template-columns: 1.2fr 1fr; gap: 12px; }
  @media (max-width: 560px) { .wrap { grid-template-columns: 1fr; } }
  #page { background: #fff; border-radius: 10px; padding: 10px; font-size: 13px; }
  #page header, #page footer { color: #6b7280; }
  .block { border: 1.5px dashed #c3c8d1; border-radius: 8px; padding: 6px 8px; margin: 8px 0; }
  .block h2, .block h3, .block h4 { margin: 0 0 4px; font-size: 14px; }
  .block p { margin: 0; }
  section.block { border-color: #2563eb; background: #eff5ff; }
  article.block { border-color: #16a34a; background: #effaf3; }
  .row { display: flex; gap: 6px; align-items: center; margin: 4px 0 8px; flex-wrap: wrap; }
  .row b { min-width: 70px; }
  select { font: inherit; padding: 2px 4px; }
  .panel { background: #fff; border-radius: 10px; padding: 10px 12px; font-size: 13px; }
  .panel h3 { margin: 10px 0 6px; font-size: 14px; }
  .lists h3 { margin-top: 4px; }
  @media (max-width: 560px) { .lists { display: grid; grid-template-columns: 1.3fr 1fr; gap: 8px; } }
  ul { margin: 0; padding-left: 18px; line-height: 1.6; }
  #outline li { list-style: none; margin-left: -18px; font-family: ui-monospace, Consolas, monospace; font-size: 12px; }
  .skip { color: #9a3412; font-weight: 600; }
  label { display: block; margin: 5px 0; }
</style>
</head>
<body>
<div class="wrap">
  <div>
    <div id="page">
      <header>Site header</header>
      <main>
        <h1>Acme Notes</h1>
        <div class="block" id="b1"><h2 id="t1">Features</h2><p>What the app does.</p></div>
        <div class="block" id="b2"><h2 id="t2">Pricing</h2><p>Three plans.</p></div>
        <div class="block" id="b3"><h2 id="t3">Release 2.4 is out</h2><p>A news post that makes sense on its own.</p></div>
      </main>
      <footer>Site footer</footer>
    </div>
    <div class="panel" style="margin-top:12px">
      <div class="row"><b>Features</b><select data-for="b1"><option>div</option><option selected>section</option><option>article</option></select></div>
      <div class="row"><b>Pricing</b><select data-for="b2"><option>div</option><option selected>section</option><option>article</option></select></div>
      <div class="row"><b>News post</b><select data-for="b3"><option>div</option><option>section</option><option selected>article</option></select></div>
      <label><input type="checkbox" id="named"> Name sections with aria-labelledby</label>
      <label><input type="checkbox" id="h4"> Make the news heading an h4</label>
    </div>
  </div>

  <div class="panel">
    <div class="lists">
      <div><h3>Landmarks</h3><ul id="landmarks"></ul></div>
      <div><h3>Articles</h3><ul id="articles"></ul></div>
    </div>
    <h3>Heading outline</h3>
    <ul id="outline"></ul>
  </div>
</div>

<script>
  // Browser rule: a section is a "region" landmark only when it has an accessible name.
  // An article is its own role (not a landmark). A div has no role.
  const page = document.getElementById('page');
  const $ = (id) => document.getElementById(id);

  function retag(el, tag) {
    if (el.tagName.toLowerCase() === tag) return;
    const n = document.createElement(tag);
    for (const a of el.attributes) n.setAttribute(a.name, a.value);
    while (el.firstChild) n.appendChild(el.firstChild);
    el.replaceWith(n);
  }

  function update() {
    // apply the choices
    document.querySelectorAll('select').forEach((s) => retag($(s.dataset.for), s.value));
    retag($('t3'), $('h4').checked ? 'h4' : 'h2');
    document.querySelectorAll('.block').forEach((b) => {
      const heading = b.querySelector('h2, h3, h4');
      if ($('named').checked && b.tagName === 'SECTION') b.setAttribute('aria-labelledby', heading.id);
      else b.removeAttribute('aria-labelledby');
    });

    // landmarks
    const lm = ['banner'];
    lm.push('main');
    page.querySelectorAll('section[aria-labelledby]').forEach((s) => {
      lm.push('region "' + $(s.getAttribute('aria-labelledby')).textContent + '"');
    });
    lm.push('contentinfo');
    $('landmarks').innerHTML = lm.map((t) => '<li>' + t + '</li>').join('');

    // articles
    const arts = [...page.querySelectorAll('article')].map((a) => a.querySelector('h2, h3, h4').textContent);
    $('articles').innerHTML = arts.length ? arts.map((t) => '<li>' + t + '</li>').join('') : '<li>none</li>';

    // headings: the level is the number in the tag, not the nesting
    let last = 0;
    $('outline').innerHTML = [...page.querySelectorAll('h1, h2, h3, h4, h5, h6')].map((h) => {
      const lvl = +h.tagName[1];
      const skipped = lvl > last + 1;
      last = lvl;
      return '<li style="padding-left:' + (lvl - 1) * 14 + 'px">' + h.tagName.toLowerCase() + ' ' + h.textContent +
        (skipped ? ' <span class="skip">skips a level</span>' : '') + '</li>';
    }).join('');
  }

  document.querySelectorAll('select, input').forEach((el) => el.addEventListener('change', update));
  update();
</script>
</body>
</html>
Change each block between div, section and article. The landmark list, the article list and the heading outline update as you go.

Two things stand out. A section only shows up as a landmark once it is named. And the heading outline follows the number in each tag, not the nesting. The rest of this page explains both.

section vs article vs div

Each element answers a different question about the content inside it.

article stands alone, section is a part with a heading, div carries no meaning.
article stands alone, section is a part with a heading, div carries no meaning.
<article> <section> <div>
Means Complete on its own A themed part of something Nothing
Heading Usually Yes Not expected
Examples Blog post, product card, comment Chapter, Features, Pricing Grid row, card frame
Browser role article region, only when named none
Default look A plain block A plain block A plain block

The "complete on its own" test for article is concrete. Imagine the block copied into an RSS feed or another site. A blog post still reads fine there. A block called "Pricing" does not; it needs the page around it.

For the wider set of meaningful tags, see semantic HTML. The page-level wrappers have their own guides: main, nav and aside.

When a section becomes a region landmark

Screen readers can list the landmarks of a page and jump straight to one. header, main and footer at the top level are landmarks on their own. A section is different: it becomes a region landmark only when it has an accessible name.

The heading inside does not name the section. aria-labelledby does.
The heading inside does not name the section. aria-labelledby does.

A heading inside the section does not name it automatically. Point at the heading with aria-labelledby:

<section aria-labelledby="pricing-title">
  <h2 id="pricing-title">Pricing</h2>
  <p>Three plans, billed monthly.</p>
</section>

aria-label="Pricing" also works when there is no visible heading to point at. aria-label covers the difference between the two.

Name only the parts a visitor would want to jump to. If every small section is a region, the landmark list grows long and stops being a shortcut.

Headings inside sections

The HTML standard once described an outline algorithm where an h1 inside a section counted as a lower level. That algorithm has since been removed from the standard. An h1 is level 1 wherever it sits.

Nesting does not renumber headings. Write the level you mean.
Nesting does not renumber headings. Write the level you mean.

So the rule is simple. Keep one h1 for the page, as the h1 guide explains.

Use h2 for top-level sections and articles, and h3 inside those. Do not jump from h2 to h4 because the smaller size looks better; change the size with CSS instead.

In the first example, tick "Make the news heading an h4" to see a skipped level flagged in the outline.

Pick one in three questions

The order of the questions matters. Styling-only wrappers are ruled out first, then standalone content, then themed parts.

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>section, article or div?</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; font-size: 14px; }
  fieldset { border: 0; background: #fff; border-radius: 10px; padding: 10px 12px; margin: 0 0 10px; }
  legend { font-weight: 700; padding: 0; float: left; width: 100%; margin-bottom: 6px; }
  legend small { display: block; font-weight: 400; color: #6b7280; }
  label { margin-right: 14px; white-space: nowrap; }
  #result { background: #fff; border-radius: 10px; padding: 12px; border-left: 5px solid #16a34a; }
  #tag { font: 700 20px ui-monospace, Consolas, monospace; color: #0f5132; }
  #why { margin: 6px 0 8px; line-height: 1.45; }
  pre { margin: 0; background: #1d2330; color: #e5e7eb; border-radius: 8px; padding: 10px; font-size: 12px; overflow-x: auto; }
</style>
</head>
<body>
<fieldset>
  <legend>1. Is the wrapper only there for CSS or layout?<small>A flex row, a card border, a max-width box.</small></legend>
  <label><input type="radio" name="style" value="yes"> Yes</label>
  <label><input type="radio" name="style" value="no" checked> No</label>
</fieldset>
<fieldset>
  <legend>2. Would it make sense on its own?<small>Pulled out into a feed or another page, it still reads complete.</small></legend>
  <label><input type="radio" name="alone" value="yes"> Yes</label>
  <label><input type="radio" name="alone" value="no" checked> No</label>
</fieldset>
<fieldset>
  <legend>3. Does it have its own heading?<small>Or could you write one that names the part.</small></legend>
  <label><input type="radio" name="heading" value="yes" checked> Yes</label>
  <label><input type="radio" name="heading" value="no"> No</label>
</fieldset>

<div id="result">
  Use <span id="tag"></span>
  <p id="why"></p>
  <pre id="code"></pre>
</div>

<script>
  const answers = {
    div: ['<div>', 'It groups things for styling only. A div adds no meaning, which is right here.',
      '<div class="cards">\n  <article class="card">...</article>\n  <article class="card">...</article>\n</div>'],
    article: ['<article>', 'It is complete on its own: a post, a product card, a comment. Give it a heading.',
      '<article>\n  <h2>Release 2.4 is out</h2>\n  <p>Dark mode, faster search...</p>\n</article>'],
    section: ['<section>', 'It is one themed part of something bigger, and the heading names that part.',
      '<section aria-labelledby="pricing-title">\n  <h2 id="pricing-title">Pricing</h2>\n  <p>Three plans...</p>\n</section>'],
    none: ['<div> or no wrapper', 'No heading and nothing to style means nothing to group. A section with no heading is just a div with extra letters.',
      '<p>Plain text can sit directly\n   in its parent.</p>'],
  };

  function pick() {
    const v = (n) => document.querySelector('input[name="' + n + '"]:checked').value;
    // order matters: styling first, then standalone, then heading
    if (v('style') === 'yes') return 'div';
    if (v('alone') === 'yes') return 'article';
    if (v('heading') === 'yes') return 'section';
    return 'none';
  }

  function show() {
    const [tag, why, code] = answers[pick()];
    document.getElementById('tag').textContent = tag;
    document.getElementById('why').textContent = why;
    document.getElementById('code').textContent = code;
  }

  document.querySelectorAll('input').forEach((i) => i.addEventListener('change', show));
  show();
</script>
</body>
</html>
Answer three questions and get the element to use, with example markup.
  1. Is it only for CSS or layout? Use div.
  2. Would it make sense on its own? Use article.
  3. Is it a themed part with its own heading? Use section.
  4. None of the above? A div, or no wrapper at all.

Nesting article and section, header and footer

The two elements nest both ways, and both shapes are correct.

  • Sections inside an article: a long post split into chapters. Each chapter is a section with an h3.
  • Articles inside a section: a "Latest posts" section holding several post cards, or a comments section holding one article per comment.
  • Articles inside an article: comments are often nested in the post they belong to. Each comment is still complete on its own.

A header or footer can sit inside an article or section too. There it holds that block's title, byline or tags. It is not the page banner or page footer landmark.

Only a header and footer outside article, aside, main, nav and section play that role.

<article>
  <header>
    <h2>How we cut build time in half</h2>
    <p>By Dana Lee</p>
  </header>
  <section>
    <h3>What was slow</h3>
    <p>...</p>
  </section>
  <footer>Filed under: tooling</footer>
</article>

A finished example: a blog page

This page puts it together: posts as articles, chapters as sections, a named comments section, and each comment as a nested article.

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>Field Notes blog</title>
<style>
  body { margin: 0; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; font-size: 14px; line-height: 1.5; }
  .top { background: #1d2330; color: #fff; padding: 10px 14px; display: flex; justify-content: space-between; align-items: center; gap: 10px; }
  .top h1 { margin: 0; font-size: 18px; }
  .top label { font-size: 12px; white-space: nowrap; }
  main { max-width: 640px; margin: 0 auto; padding: 12px; }
  article, section { background: #fff; border-radius: 10px; padding: 10px 14px; margin: 0 0 12px; }
  article section, article article { background: #f8f9fb; padding: 6px 12px; margin: 8px 0; }
  h2 { font-size: 18px; margin: 4px 0 0; }
  h3 { font-size: 15px; margin: 6px 0 2px; }
  p { margin: 4px 0; }
  .meta, .by { color: #6b7280; font-size: 12px; }
  article footer { font-size: 12px; color: #6b7280; border-top: 1px solid #e5e7eb; padding-top: 6px; margin-top: 8px; }
  /* "Show the tags" mode: outline each element and print its name */
  .tags article, .tags section { outline: 2px dashed #16a34a; position: relative; padding-top: 22px; }
  .tags section { outline-color: #2563eb; }
  .tags article::before, .tags section::before {
    position: absolute; top: 3px; left: 8px; font: 700 11px ui-monospace, Consolas, monospace;
  }
  .tags article::before { content: "article"; color: #0f5132; }
  .tags section::before { content: "section"; color: #1d4ed8; }
  .tags section[aria-labelledby]::before { content: "section = region landmark"; }
</style>
</head>
<body>
<header class="top">
  <h1>Field Notes</h1>
  <label><input type="checkbox" id="show"> Show the tags</label>
</header>

<main>
  <article>
    <header>
      <h2>How we cut build time in half</h2>
      <p class="meta">By Dana Lee, <time datetime="2026-09-20">20 September 2026</time></p>
    </header>

    <section>
      <h3>What was slow</h3>
      <p>Every build downloaded the same packages again.</p>
    </section>
    <section>
      <h3>The fix</h3>
      <p>A shared cache, keyed by the lock file.</p>
    </section>

    <footer>Filed under: tooling</footer>

    <section aria-labelledby="comments-title">
      <h3 id="comments-title">Comments (2)</h3>
      <article>
        <p class="by"><b>Sam</b> wrote:</p>
        <p>Did you try it on the CI runners too?</p>
      </article>
      <article>
        <p class="by"><b>Dana Lee</b> wrote:</p>
        <p>Yes, same result there.</p>
      </article>
    </section>
  </article>

  <article>
    <h2>Three tools we stopped using</h2>
    <p class="meta"><time datetime="2026-09-02">2 September 2026</time></p>
    <p>A short post with no parts, so no sections inside.</p>
  </article>
</main>

<script>
  document.getElementById('show').addEventListener('change', (e) => {
    document.body.classList.toggle('tags', e.target.checked);
  });
</script>
</body>
</html>
Tick "Show the tags" to outline every article and section. The named comments section is the only region landmark.
  • Page: a top header with the blog name in h1, then main.
  • Posts: one article each, with the title in h2.
  • Chapters: section elements with h3, not named, so the landmark list stays short.
  • Comments: a section named with aria-labelledby, holding one article per comment.
  • Short post: no sections at all, because it has no parts.

Sections with an id also make good jump targets for links inside the page. Link to a section of a page shows how.

When not to use section

A section is not a styled div. These are signs it should be something else:

  • You wrapped it for a background colour, padding or a flex row. That is a div.
  • It has no heading and you cannot think of one. It is not a themed part. Use a div, or drop the wrapper.
  • It is the only wrapper around the whole page content. That is main.
  • It is a sidebar, pull quote or related-links box. That is aside.
  • It is a list of navigation links. That is nav.

A div is never wrong for layout. Using one where no meaning is intended is better than a section that claims a meaning it does not have.

When it does not work

What you see Cause Fix
The section is missing from the landmark list It has no accessible name Add aria-labelledby pointing at its heading id
aria-labelledby is set but no name appears The id does not match the heading Check the spelling of both ids
Dozens of region landmarks Every styling wrapper became a named section Use div for wrappers; name only key parts
A validator warns about a section with no heading The section is a styling box Add a heading, or change it to div
A card list reads as many articles that make no sense alone article used for fragments Keep article for standalone items
The heading outline jumps from h2 to h4 A level was picked for its size Use the next level and resize with CSS
Every part reads as level 1 h1 in each section Use h2 and h3 to match the nesting

Structure is easier to show than to explain. A screenshot hides the tags, and an .html attachment may open as plain code, or not at all, on a phone.

To send a page that works, 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 switch the blocks and tick the boxes themselves. If you change the code later, the same link shows the new version.

Questions people ask

What is the section tag used for in HTML?

For one themed part of a larger whole that has its own heading: the chapters of a long post, the Features and Pricing parts of a landing page, or a tab panel. The heading names what the part is about.

What is the difference between section and article?

An article is complete on its own. A blog post, a product card or a comment still makes sense if you copy it into a feed or another page. A section only makes sense as part of the thing around it.

What is the difference between section and div?

A div has no meaning; it exists for styling and scripting. A section says "this is a themed part with a heading", and when it has an accessible name it becomes a region landmark that screen reader users can jump to.

Does a section need a heading?

It should have one. A section without a heading is usually a sign that a div was meant. If the part really has no heading, a div, or no wrapper at all, is the better choice.

Does an h1 inside a section count as a lower level?

No. The HTML standard no longer has an outline algorithm that renumbers headings by nesting. An h1 is level 1 wherever it sits, so pick h2, h3 and so on to match the structure.

Keep reading