Skip to main content
    Back to blog
    DevelopmentPublished on October 9, 2026

    Semantic HTML and Alt Text: The Foundation of an Accessible Website

    Why a button should be a button, how to mark up landmarks and headings, and how to write alt text for photos, icons and charts — with code examples

    Semantic HTML and Alt Text: The Foundation of an Accessible Website

    Part 2 of 8 in the "Accessible Web in Practice" series. Previous part: Web Accessibility: Who It Really Serves and Where to Start.

    Most accessibility comes for free if you use the right HTML elements. This part covers buttons and links, landmarks and headings, and alternative text for images, SVG icons and video.

    Semantic HTML — the foundation of everything

    The right HTML element gives you role, name, state, keyboard support and the behavior assistive technology users expect — for free. The most common mistake is trying to rebuild all that by hand.

    A button is a <button>

    <!-- ❌ Bad: a div pretending to be a button -->
    <div class="btn" onclick="save()">Save</div>
    
    <!-- ✅ Good -->
    <button type="button" class="btn" onclick="save()">Save</button>
    

    The <div> above has no role (a screen reader just reads the text), isn't in the tab order and doesn't respond to Enter or Space. To "fix" it you'd need this:

    <!-- Possible, but why? -->
    <div role="button" tabindex="0" class="btn"
         onclick="save()"
         onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); save(); }">
      Save
    </div>
    

    And it's still worse than a real button: no disabled support, no form submission, no correct Space behavior (a real button fires on key release). The conclusion is simple: use <button>. Default styles reset with all: unset or a couple of properties.

    Link or button?

    The rule: a link goes somewhere, a button does something.

    • Navigating to another page or anchor — <a href="...">.
    • Opening a menu, submitting a form, toggling a theme, deleting a record — <button>.
    <!-- ❌ A link that doesn't go anywhere -->
    <a href="#" onclick="openMenu()">Menu</a>
    
    <!-- ✅ -->
    <button type="button" aria-expanded="false" aria-controls="main-menu">Menu</button>
    

    Screen reader users expect different behavior from links and buttons, and a link without href has neither a role nor focus.

    Page structure: landmarks

    HTML5 sectioning elements automatically become landmarks, which screen reader users jump between with a single key:

    <body>
      <a class="skip-link" href="#main">Skip to main content</a>
    
      <header>                     <!-- banner -->
        <a href="/"><img src="logo.svg" alt="Agama Labs home"></a>
        <nav aria-label="Main">    <!-- navigation -->
          <ul>
            <li><a href="/services" aria-current="page">Services</a></li>
            <li><a href="/portfolio">Portfolio</a></li>
            <li><a href="/contact">Contact</a></li>
          </ul>
        </nav>
      </header>
    
      <main id="main">             <!-- main -->
        <h1>Services</h1>
        ...
      </main>
    
      <aside aria-labelledby="related-title"> <!-- complementary -->
        <h2 id="related-title">Related articles</h2>
        ...
      </aside>
    
      <footer>                     <!-- contentinfo -->
        ...
      </footer>
    </body>
    

    A few details:

    • One <main> per page.
    • If a page has several <nav> elements, label them (aria-label="Main", aria-label="Breadcrumbs"), or the user hears "navigation, navigation."
    • aria-current="page" tells users which page they're on. It's also a convenient styling hook: nav a[aria-current="page"] { ... }.

    Headings

    Headings are the main way to navigate. In WebAIM's survey of screen reader users, most respondents find information on a page through headings (WebAIM Screen Reader Survey).

    • Levels reflect structure, not font size. Need a small heading? Use <h2> and make it smaller in CSS.
    • Don't skip levels: <h2> is followed by <h3>, not <h4>.
    • Usually one <h1> per page — the page title.
    • Don't turn text into a heading just because it should be bold.

    You can check the outline with an extension like HeadingsMap or a screen reader's elements list.

    Lists, tables and the rest

    • A set of similar items (menu items, cards, steps) is a <ul>/<ol>. The screen reader announces "list, 5 items," and the user immediately knows the scope.
    • Tabular data goes in a <table> with <th scope="col"> / <th scope="row"> and a <caption>. Don't use tables for layout.
    • Document language — <html lang="en">. Without it, a screen reader may read your text with the wrong voice and pronunciation. Mark up passages in other languages: <span lang="es">bienvenidos</span>.
    • Each page gets a unique <title>: "Services — Agama Labs," not the same "Agama Labs" everywhere.

    Images, icons and alternative text

    Missing alt is the second most common error on the web. But "add some alt, any alt" isn't the fix either.

    How to write alt text

    Ask yourself: what would someone lose if they couldn't see this image?

    Image type What goes in alt
    Informative (product photo, diagram) The point of the image in the context of the page
    Functional (image inside a link or button) Where it goes or what it does, not what it looks like
    Decorative (background pattern, mood photo) Empty alt=""
    Complex (chart, infographic) Short alt + a full description in the text nearby
    Image of text The same text
    <!-- Informative -->
    <img src="dashboard.png"
         alt="Orders dashboard: a table with each order's number, status and total">
    
    <!-- Functional: logo link -->
    <a href="/"><img src="logo.svg" alt="Agama Labs home"></a>
    
    <!-- Decorative: empty alt, not missing alt -->
    <img src="wave.svg" alt="">
    
    <!-- Complex: short alt + description -->
    <figure>
      <img src="sales-2026.png" alt="2026 sales chart, details below">
      <figcaption>
        Sales grew from 120 to 340 orders a month between January and June,
        dipped to 260 over the summer and returned to 330 in September.
      </figcaption>
    </figure>
    

    An important difference: an empty alt="" and a missing alt are not the same. Empty says "decorative, skip it." Missing makes the screen reader fall back to the file name: "IMG underscore 4021 dot jpg."

    Don't start alt text with "image of…" or "picture of…" — the screen reader already announces the role.

    SVG icons

    <!-- Decorative icon next to text: hide it -->
    <button type="button">
      <svg aria-hidden="true" focusable="false" width="16" height="16">
        <use href="#icon-trash"></use>
      </svg>
      Delete
    </button>
    
    <!-- Icon-only button: a name is required -->
    <button type="button" aria-label="Delete order #1042">
      <svg aria-hidden="true" focusable="false" width="16" height="16">
        <use href="#icon-trash"></use>
      </svg>
    </button>
    
    <!-- Standalone informative SVG -->
    <svg role="img" aria-labelledby="chart-title" viewBox="0 0 200 100">
      <title id="chart-title">Conversion rose from 2% to 5%</title>
      ...
    </svg>
    

    An icon-only button with no name is exactly the "empty button" from the WebAIM stats (30.6% of pages). The screen reader just says "button."

    An alternative to aria-label is visually hidden text. Automatic translation handles it better, and it's more reliable for voice control:

    /* Hide visually, keep available to screen readers */
    .visually-hidden {
      clip-path: inset(50%);
      height: 1px;
      width: 1px;
      overflow: hidden;
      position: absolute;
      white-space: nowrap;
    }
    
    <button type="button">
      <svg aria-hidden="true" focusable="false">...</svg>
      <span class="visually-hidden">Delete order #1042</span>
    </button>
    

    Don't use display: none or visibility: hidden for this kind of text — they hide it from screen readers too.

    Video and audio

    • Videos with speech need captions (<track kind="captions">). Check auto-generated captions by hand.
    • Audio (a podcast) needs a text transcript.
    • If important information appears only visually in a video (a phone number on screen that nobody says out loud), you need audio description or a text description.
    • No autoplay with sound.
    <video controls>
      <source src="demo.mp4" type="video/mp4">
      <track kind="captions" src="demo.en.vtt" srclang="en" label="English" default>
    </video>
    

    The "Accessible Web in Practice" series

    1. Web Accessibility: Who It Really Serves and Where to Start
    2. Semantic HTML and Alt Text: The Foundation of an Accessible Website — you are here
    3. Accessible Forms: Labels, Hints, Errors and Autofill
    4. Keyboard and Focus: Building a Website You Can Use Without a Mouse
    5. Contrast, Motion and Button Size: Visual Accessibility
    6. ARIA and Dynamic Content: When Attributes Help and When They Hurt
    7. Accessibility in React: IDs, Focus on Route Change and Announcements
    8. How to Test Accessibility: Automation, Manual Checks and a Checklist

    Next part: Accessible Forms: Labels, Hints, Errors and Autofill

    Agama Labs builds websites and web applications. Want to check how accessible your site is? Write to us: hello@agamalabs.com

    Contact

    Let’s talk about your project

    Leave your details and we’ll get back to you within 2 business hours for a free consultation. Tell us what you’re building - we’ll help you choose the right approach.

    Business hours

    Mon–Fri: 08:00 AM – 5:00 PM (PST)
    Sat–Sun: Closed

    Request a call

    Fill out the form and we’ll reach out shortly

    By clicking the button, you agree to our Privacy Policy

    We value your privacy

    We use analytics cookies, including third-party ones, to see how the site is used and improve it