Semantic HTML and ARIA

This guides is an overview about semantic HTML markup, it's importance for web accessibility and important ARIA (Accessible Rich Internet Applications) techniques.

Semantic HTML markup

Writing semantic HTML means using the elements that correctly represent the purpose of the functionality you are developing, rather than just it's presentation. For example, using a <p> tag to wrap a paragraph rather than <div class="text"> is good semantics because the machine understands that's a paragraph and is able to treat it as such, while the <div> tag has no semantic value and means nothing to computers and screen readers unless you specific add a role to it (more later on this article).

Using the correct tags on the correct situations is considered good practices in front-end development, but it also has many other positive effects, such as:

  • Search Engines: able to interpret your content correctly and rank you better
  • Scripting: making custom scripts such as content crawlers is a lot easier when you know what to expect out of a page content
  • Browser extensions: enable all sorts of user customization, some people use browser extensions to change colors for better contrast, for example.
  • Screen readers: are able to deliver the correct information to users since they know exactly what's the purpose of each component

There are over 100 semantic HTML elements to choose from, you should become familiar with them so you are aware of what's available to you when you develop your next functionality.

  • Glance over the HTML elements list every once in a while
  • Try really hard to find a semantic element that suits your needs
  • Only rely on divs or spans if you definitely ran out of options

Examples of semantic HTML tags that are usually overlooked and underused

  • Header tags <h1> through <h6>
  • <blockquote>
  • <address>
  • <time>
  • <em> and <strong>
  • <del> and <ins>
  • scope attributes on table cells

Comparison of poor versus good semantics

Poor semantics:

  • Overly reliant on semantically empty <div> and <span>
  • Wrong usage of paragraphs
  • Not using interactive elements
<div class="navigation">
  <span class="user-name">John Doe</span>
  <div class="open">
    <p><a class="hcr-link-10-8-1" href="#">Profile</a></p>
    <p><a class="active" href="#">Settings</a></p>
    <p><a class="hcr-link-10-8-1" href="#">Log out</a></p>
  </div>
</div>

Good semantics:

  • Use <nav> to represent navigation;
  • Always use a <button> for interactive elements that trigger actions
  • Use relevant list elements <ul> or <ol> for lists of items
  • Don't use <p> just for appearance
<nav class="navigation">
  <button class="user-name">John Doe</button>
  <ul class="open">
    <li>
      <a class="hcr-link-10-8-1" href="#">Profile</a>
    </li>
    <li>
      <a class="active" href="#">Settings</a>
    </li>
    <li>
      <a class="hcr-link-10-8-1" href="#">Log out</a>
    </li>
  </ul>
</nav>

ARIA

ARIA stands for Accessible Rich Internet Applications and the purpose of ARIA roles and attributes is to enhance the semantics of HTML by:

  • Adding names and labels to elements and landmarks:
    • aria-label: replaces the current label of an element (it does not add to it), that should be used if the current label is inadequate. Not all elements accept aria-label.
    • aria-labelledby: works the same as aria-label, by substitution, but allows you to vinculate with another element, e.g.: connecting landmarks to their headings.
    • aria-describedby: adds contents from another element to the current label of the element it's applied to. E.g.: the "Reserve 1 seat" button at the search results page should point to the price element to add the price text to it and create a label of: “Button, reserve 1 seat, $14.99”.
  • Outline the semantic relationship between elements that goes beyond parent to child. E.g.:
    • tabs that control a tabbed container bellow;
    • a button that has a sub-menu.
  • Announce changes in state, such as:
    • active state;
    • busy state;
    • expanded state;
    • pressed state.
  • Create live regions to be announced as they get updates, for example:
    • error messages;
    • notifications;
    • chat messages.

ARIA roles

With ARIA you can redefine the element role, for example, you may use a list to create the tabs of a tabbed content then associate the tab role to each list item.

You can also remove a role from the element by passing role="presentation", which is useful in some situations where you're using an element that's not fit for the job, such as a table.

An ARIA role is set via the role attribute added to an element, and must never change at any time after set.

There are numerous different roles that will enable the creation of even the most complex component out there, if you are willing to do your research. Please check this MDN list of all available ARIA roles.

ARIA attributes

There are even more attributes out there to choose from, you must be aware of the existence of these attributes and how they relate with each role since specific roles will support specific attributes and the correct implementation of them is what will truly enable a good experience for users of assistive technologies.

Please check this MDN list of all available ARIA attributes and get familiar with their usage.

Example with ARIA

This next example expands and incorporates a few of ARIA attributes to enhance the semantics of our profile sub-menu and deliver an accessible experience.

Notice:

  • The button with the user name has an id, which is passed as aria-labelledby to the navigation landmark;
  • The button has aria-haspopup="true" that indicates that it controls a sub-menu;
  • Machines know that the sub-menu is the following list with an id="user-menu" because it's passed as aria-controls value to the button that as a sub-menu already declared;
  • The button tells machines wether the sub-menu is expanded or collapsed because it as aria-expanded="true|false";
  • We know that we are currently on the "Settings" page because it has aria-current="page" set to it.
<nav aria-labelledby="user-menu-button" class="navigation">
  <button class="user-name" id="user-menu-button" aria-haspopup="menu" aria-controls="user-menu" aria-expanded="true">
    John Doe
  </button>
  <ul id="user-menu" class="open">
    <li>
      <a class="hcr-link-10-8-1" href="#">
        Profile
      </a>
    </li>
    <li>
      <a class="active" href="#" aria-current="page">
        Settings
      </a>
    </li>
    <li>
      <a class="hcr-link-10-8-1" href="#">
        Log out
      </a>
    </li>
  </ul>
</nav>