Back to the blog

Semantic HTML Still Matters

Landmarks, headings, and native elements that make pages easier to use, style, and maintain.


Semantic HTML Still Matters

Div soup still ships in production because it feels faster in the moment. Semantic HTML pays that time back in accessibility, SEO, and CSS that does not fight the DOM.

Use landmarks on every page

<header>, <nav>, <main>, and <footer> give screen readers a map of the page. Skip links and focus management work better when main actually wraps the primary content—and when there is only one main per document.

I treat landmarks as a checklist when I scaffold a layout, not as decoration after the design is done:

  1. One site-wide <header> for branding and primary chrome
  2. <nav> for major navigation (and another for in-page or footer links when it helps)
  3. A single <main> for the page’s primary job
  4. <footer> for secondary links and meta
  5. <aside> only for truly complementary content, not every sidebar-looking column

Landmark misuse is common. Nested <header> elements inside every card are fine in HTML5, but wrapping the whole app in a second <main> for a modal “page” is not. Overusing <section> without headings also creates noise: a section should introduce a thematic grouping with a heading, not replace a div for styling.

When I review a Nuxt or Vue layout, I ask whether a keyboard user could jump to main content after a few Tab presses or via a skip link. If the answer is no, the landmarks are incomplete or the focus order is wrong—semantics alone will not fix a broken tab sequence, but they make the intended structure discoverable.

Headings are a table of contents

One <h1> per page, then descend in order without skipping levels for styling convenience. If you need a smaller visual size, change the CSS—not the heading level.

Assistive tech and many SEO tools build an outline from heading ranks. Skipping from h1 to h4 because “it looks right” produces a broken outline. The fix is boring and reliable:

.card-title {
  font-size: 1.125rem;
  font-weight: 600;
}
<article>
  <h2 class="card-title">Project name</h2>
</article>

The visual size lives in CSS; the rank stays honest. On blog indexes I keep the page h1 as the section title (“Blog”) and use h2 for post titles. On a post page the post title is the h1, and in-article sections use h2/h3. That pattern stays consistent across the site and makes “where am I?” obvious.

I also avoid heading-only “fake buttons.” If something is an action, it is a control. If it is a title, it is a heading. Mixing those roles is how you get clickable h3s with no keyboard affordance and no clear name for screen readers.

Reach for native elements first

Buttons for actions, links for navigation, <button type="submit"> inside forms. Native controls bring keyboard support and sensible defaults for free.

The mistakes I still catch in code review:

  • <div @click> for primary actions—no Enter/Space, no disabled semantics, no form participation
  • <a href="#"> or href="javascript:void(0)" for in-page behavior—links should go somewhere; use a button for side effects
  • Custom select/checkbox widgets rebuilt from scratch before checking whether a styled native control is enough
<article>
  <header>
    <h2>Post title</h2>
    <time datetime="2025-07-06">July 6, 2025</time>
  </header>
  <p>Body copy…</p>
</article>

Native elements also give you better hooks for CSS and testing. button:disabled, a:focus-visible, and input:invalid exist without a custom state machine. When you do need a custom widget (combobox, date picker with complex constraints), start from the ARIA Authoring Practices patterns and keep the markup honest about role and state—do not fake a button with a span and hope listeners cover every input method.

Lists deserve a mention too. If you render a set of related items—tags, steps, navigation links—<ul>/<ol> communicate the grouping. A stack of divs with bullets painted in CSS looks fine visually and fails the “what is this set?” test for assistive tech.

Style and maintain from the structure you already have

Semantic markup is not only for accessibility audits. It is a maintenance strategy. Selectors like article > header or nav a are clearer than .card-header-wrapper-inner trees that exist because everything was a div. When the DOM reflects meaning, refactors stay local: rename a class for look-and-feel without rewriting the document outline.

A practical workflow I use on portfolio and content sites:

  1. Write the HTML (or template) with landmarks and headings first
  2. Add classes only where styling or behavior needs a hook
  3. Prefer element + context selectors for stable patterns (main a, article img)
  4. Reach for ARIA only when native HTML cannot express the role or state

That order prevents the common reverse path: design a pile of boxes, then sprinkle role and aria-* to apologize. ARIA is powerful; it is also easy to get wrong. Native HTML is the cheaper default.

SEO is a side effect, not the primary goal, but it follows the same structure. Clear headings, a single main, and meaningful link text help crawlers and humans alike. Thin div trees with all meaning in data attributes force every consumer—browser, bot, or teammate—to reverse-engineer the page.

Wrap-up

Semantic HTML is cheap insurance: clearer structure for assistive tech, more predictable styling hooks, and less JavaScript reimplementing what the platform already does. Start with landmarks and an honest heading outline, prefer native controls, and treat ARIA as a last resort. The page that is easier to navigate is usually the page that is easier to ship and maintain.