Semantic HTML · 7 / 8
lesson 7

When div is still right

Not every chunk needs a semantic tag. div and span are the right call for non-semantic wrappers — and that's a feature.

~ 14 min read·lesson 7 of 8
0 / 8

After five lessons of "use the right semantic tag", you might be tempted to never write <div> or <span> again. Don't. Both elements are non-semantic on purpose, and they are the right answer in plenty of cases. The trick is knowing which.

This lesson is the counterweight to the rest of the course. Semantic HTML is a tool, not a religion. Reach for the right element when there is one; reach for <div> or <span> when there isn't.

Non-semantic by design

Two elements have no semantics: <div> (block-level) and <span> (inline). They contribute nothing to the accessibility tree. They give you a place to attach CSS, JavaScript, or an id — and that is exactly all they do.

That sounds like a downside, and beginners often treat it as one. But not every chunk on a page should contribute to the accessibility tree. A flex container that wraps three buttons is not a "region of the page" anyone wants to jump to — it is a layout helper. A wrapper that exists so a CSS animation has a target is not "a section". A <span> that lets you tint a single word is not "an emphasis".

For all of those, <div> and <span> are exactly the right call. Their job is to be invisible to assistive tech. That makes them the perfect tools for layout-only or styling-only wrappers.

layout-wrapper.html
<!-- This div is here so flexbox has something to act on. -->
<div class="row">
<button>Cancel</button>
<button>Save</button>
</div>

The <div> adds nothing semantic — and it shouldn't. The two buttons are still buttons. The accessibility tree shows two buttons; the layout helper sits silent.

If you replaced that <div> with a <section>, the screen reader would announce a region with two buttons inside. Useless. The user does not want to navigate to "the wrapper containing two buttons" — they want to find the buttons.

Wrapping for layout

The most common job for a <div> is to give CSS something to attach to. Modern layouts (flexbox, grid) need parent elements with the right display value, and those parents are often pure layout containers.

grid-card.html
<article class="card">
<header>
  <h3>Sourdough loaf</h3>
</header>
<div class="card-body">
  <p>$8 — fresh on Saturdays.</p>
</div>
<footer>
  <button>Order</button>
</footer>
</article>

The <article>, <header>, and <footer> carry meaning. The <div class="card-body"> is purely layout — it sits between the heading and the actions and exists so a CSS rule can target the middle slot. Nothing about it is "a section of the article"; it is a flex item.

If you find yourself wrapping content "just for CSS", <div> is correct. If you later realize the wrapper actually represents something — a thematic group, a navigation, an aside — then upgrade to a semantic tag. Until then, leave it generic.

Tip

A useful test: imagine reading the page through a screen reader's "list of landmarks" and "list of regions" panels. Should this wrapper appear in either list? If no, it should be a <div>. The list is more useful when it is short.

check your understanding
You have <div class="text-content"><p>...</p><p>...</p></div> wrapping the article body so you can give it a max-width via CSS. A reviewer suggests changing the div to a <section>. Should you?

role="presentation" and aria-hidden

Sometimes a native element you cannot avoid using carries a semantic you do not want. A <table> used purely for layout (rare these days, but real in legacy code) announces "table with 3 rows and 2 columns" — confusing if the cells are not actual data. A decorative SVG icon next to a label announces its <title> element when both should be silent.

Two ARIA attributes give you the escape hatch:

role="presentation" (or role="none") — strips the element's default role from the accessibility tree. The element is still rendered; it just stops announcing itself.

aria-hidden="true" — removes the element and its descendants from the accessibility tree entirely. Visually present, semantically gone.

decorative-icon.html
<button>
<svg aria-hidden="true" viewBox="0 0 16 16">
  <path d="M8 1 L15 14 L1 14 Z" />
</svg>
Save
</button>

The button announces as "Save". The decorative triangle is hidden from screen readers — the user does not need to hear "image" before "Save". A meaningful icon (one that conveys information not in the text label) would not be hidden; it would have an aria-label instead.

layout-table.html
<table role="presentation">
<tr>
  <td>Logo</td>
  <td>Site title</td>
</tr>
</table>

The role="presentation" says: ignore the table-ness, treat the cells as plain content. (For new code, do not write layout tables. This pattern exists for legacy.)

Watch out

Don't use aria-hidden="true" on focusable elements. If a button or link is hidden from the accessibility tree but still tabbable, a keyboard user can land on it and have no idea what they are activating. Either hide it from both sighted and assistive users (with CSS), or expose it to both.

Limits of native elements

Native HTML covers most patterns — buttons, links, forms, lists, sections, dialogs. But there are gaps. When the gap is real, ARIA on a <div> is the answer.

A few examples where native HTML doesn't have a built-in:

  • A custom tabs widget — there is no <tabs> element. The accessible pattern is role="tablist" on a <div>, role="tab" on each button, role="tabpanel" on each pane.
  • A drag-and-drop tree — no native tree element. role="tree" and role="treeitem" are the ARIA equivalents.
  • A combobox or autocomplete<datalist> covers the simplest cases (lesson 6 of html-forms touches it), but a full search-as-you-type combobox needs role="combobox", role="listbox", and a small ARIA dance.

For these, the recipe is the same: start from a <div>, add the role, manage focus with tabindex and JavaScript, expose state with ARIA attributes (aria-expanded, aria-selected, aria-controls). It is real work, and it is the right call when no native element fits.

The first rule of ARIA still holds: do not use ARIA. The corollary: when you must, use it precisely. ARIA written sloppily is worse than nothing — it lies to the accessibility tree and trips users.

check your understanding
You're building a tabs widget — three tabs, three panels. There's no native <tabs> element. The right approach is:
check your understanding
You have a decorative star icon next to "Premium" in a label. The star carries no information — it's decoration. The right markup is:
check your understanding
A teammate wraps every CSS-flex layout helper in <section> "to be more semantic". Three weeks later, a screen-reader user reports the page has 40 unnamed regions. What is the cleanest fix?
← prevnext lesson →
KeepLearningcertificate
for completing
Semantic HTML
0 of 8 read