Frontend / Accessibility / 02_semantic_html_and_landmarks.md

Semantic HTML and Landmarks

Updated 5 interview angles 5 min read source
On this page10 Why prefer over ?
  • What are landmarks and why do they matter?
  • How should headings be structured?
  • When is a / the right choice?
  • What semantic elements are commonly under-used?
  • vs — when each?
  • Gotchas
  • Related
  • Further reading
  • Interview angle
  • Semantic HTML and Landmarks

    Semantic HTML is ~80% of accessibility for free. Native elements (<button>, <a>, <nav>, <main>, <h1>–<h6>, <label>, <table>) come with the correct role, keyboard behavior, focusability, and states that assistive tech relies on. A <div onClick> has none of that. Use the right element first; reach for ARIA only to fill genuine gaps. Landmarks (header, nav, main, aside, footer) let screen-reader users jump between page regions; a logical heading hierarchy is how they skim.

    Why prefer <button> over <div onClick>?

    A native <button> is, for free:

    • focusable (in the tab order) — a <div> is not without tabindex="0",
    • operable by keyboard — Enter/Space activate it; a <div> needs manual keydown handling,
    • announced as “button” with its label by screen readers — a <div> is announced as nothing,
    • equipped with disabled semantics and form participation.
    html
    <!-- Faking it, and still not equivalent. -->
    <div role="button" tabindex="0"
         @click="save"
         @keydown.enter="save"
         @keydown.space.prevent="save">
      Save
    </div>
    
    <!-- All of that free, plus disabled and form submit. -->
    <button type="submit">Save</button>

    To match a <button> with a <div> you’d need role="button", tabindex="0", and key handlers for Enter and Space — and you’d still miss edge cases. Just use <button>.

    What are landmarks and why do they matter?

    Landmark elements define page regions that screen-reader users navigate between directly (e.g., “jump to main”):

    Element Role Purpose
    <header> banner (when top-level) site header
    <nav> navigation navigation blocks
    <main> main the primary content (one per page)
    <aside> complementary tangential content
    <footer> contentinfo (top-level) site footer
    <section> with a name region named significant area
    <form> with a name form a form region

    Use one <main>. Give repeated landmarks accessible names so users can tell them apart:

    html
    <header>
      <nav aria-label="Primary">...</nav>
    </header>
    
    <main id="main">...</main>
    
    <aside aria-label="Related articles">...</aside>
    
    <footer>
      <nav aria-label="Footer">...</nav>
    </footer>

    Two unlabelled <nav> elements are announced identically, so the landmark list reads “navigation, navigation” and helps nobody. And <main id="main"> is what the skip link needs — <a href="#main" class="skip">Skip to content</a> as the first focusable element on the page.

    How should headings be structured?

    One <h1> describing the page, then a logical, non-skipping hierarchy (h1 → h2 → h3, don’t jump h2→h4). Headings are the screen-reader equivalent of skimming — users list and jump by heading. Don’t pick a heading level for its font size (style with CSS); pick it for document structure.

    When is a <div>/<span> the right choice?

    For styling/layout containers with no semantic meaning. <div> and <span> have no implicit role, which is correct when the element is purely presentational. Problems arise only when you put behavior (click handlers, “buttons,” “links”) on them.

    What semantic elements are commonly under-used?

    <button type="button"> (vs div), <a href> for navigation (vs button/div with JS), <label> tied to inputs, <fieldset>/<legend> for groups, <table> with <th scope> for tabular data, <ul>/<ol>/<li> for lists, <nav>/<main>/<aside>, <details>/<summary> for native disclosure, <dialog> for modals, <time>, <figure>/<figcaption>. Each carries semantics ARIA would otherwise have to fake.

    <button> vs <a> — when each?

    <a href> navigates (changes URL / location) and should be a link. <button> performs an action in place (submit, toggle, open menu). Misusing them breaks expectations: links open in new tabs, are bookmarkable, and respond to Enter; buttons respond to Enter and Space and aren’t navigable destinations. A “link” with role="button" is usually a sign you picked the wrong element.

    Gotchas

    • tabindex > 0 is an anti-pattern — it jumps the element ahead of natural order and creates confusing focus. Use 0 (in natural order) or -1 (focusable only programmatically).
    • role="presentation"/aria-hidden on interactive elements removes them from the a11y tree but they remain keyboard-focusable — a trap. Don’t hide focusable things.
    • Headings used for size produce nonsense outlines for screen readers — keep level = structure, size = CSS.
    • Multiple <main> or <h1> confuses landmark/heading navigation.
    • Generic link text (“click here”, “read more” ×10) is useless out of context — screen-reader users list links; make them self-describing.
    • <section> without an accessible name is not exposed as a landmark — name it or use a plain element.

    Further reading

    Interview angle 5

    • “Why does the element you choose matter?” - a <button> comes with a role, keyboard activation on Enter and Space, focusability, and disabled-state semantics. A <div onClick> has none of those, so recreating it means tabindex, two key handlers, a role, and focus styles - all of which you can get wrong and none of which you needed.
    • “What are landmarks for?” - navigation. Screen reader users jump between regions rather than reading linearly, so <main>, <nav>, <header> and <footer> are a table of contents for the page. One <main> per page, and name duplicate <nav>s with aria-label so “navigation” and “navigation” are distinguishable.
    • “How should headings be used?” - as document structure, because skimming by heading is one of the primary ways the page gets navigated. Choose the level by meaning and the size by CSS, and do not skip levels - a jump from h2 to h4 reads as a missing section.
    • “<a> or <button>?” - <a> navigates somewhere and has a URL you can copy, open in a new tab or bookmark. <button> performs an action. Getting this backwards breaks middle-click and right-click on links, and breaks Space activation on buttons.
    • “What about tabindex?” - 0 to make something focusable, -1 to make it programmatically focusable but skipped in the tab order. Positive values are an anti-pattern: they jump ahead of every natural element on the page, so one tabindex="3" reorders the entire document.