Link Search Menu Expand Document

Accessibility Deep Dive

@INCOMPLETE

Table of Contents

This page does not cover ARIA, which is an additional requirement when your website uses custom JavaScript events and scripts. For how to handle those accessibly, read the ARIA Deep Dive page.

Different Levels of Compliance

All WCAG Guidelines have “A,” “AA,” and “AAA” versions. “A” is basic compliance, “AA” is good compliance, and “AAA” is ideal. The layers are nested so that reaching “AA” compliance includes all of the requirements for “A”, and “AAA” includes all of the requirements for “AA.” Compliance levels are further explained here.

At UCF, our goal is to maintain at least “AA” compliance.

For a great summary with examples of every guideline, visit the official WCAG Quick Reference Guide.

Links to the official WCAG 2.2 documentation are labeled by their Success Criteria (SC).

Principle 1: Perceivable

Information and user interface components must be presentable to users in ways they can perceive.

  • 1.1 Text Alternatives: Provide text alternatives for any non-text content so that it can be changed into other forms people need, such as large print, braille, speech, symbols or simpler language.
  • 1.2 Time-Based Media: Provide alternatives for time-based media.
  • 1.3 Adaptable: Create content that can be presented in different ways (for example simpler layout) without losing information or structure.
  • 1.4 Distinguishable: Make it easier for users to see and hear content including separating foreground from background.

Everything Should Have a Text Alternative

Users who can’t see rely entirely on text-to-speech software to use the web. But that software only works when there is text to read. Everything on your site that isn’t already text should have text alternatives (see Text Alternatives: SC 1.1.1).

Images

All images should have an alt attribute that describes the essential elements of the image. They should be short descriptions that convey whatever the image is meant to convey. If the image is purely decorative, like a background, still include alt="" or the screen reader will attempt to read the src attribute instead.

BAD Code Examples

<!-- Missing alt tag -->
<img src="image.jpg" >

GOOD Code Examples

<!-- Good, concise description -->
<img src="bread.jpg" alt="A steaming loaf of fresh bread" >

<!-- Highlight the important parts of complicated images -->
<img src="noble-gases.jpg" alt="A chart of the periodic table of the elements. The noble gasses in the last column are highlighted." >

<!-- If the image has text (see note at the end of this section), include it -->
<img src="announcement.jpg" alt="Student game night! Saturday, Jan. 20. Refreshments provided." >

<!-- When a description is not necessary, include alt="" -->
<img src="background-swirl.jpg" alt="" >

If the description already appears on the screen, you can associate it with the image using the native <figure> and <figcaption> tags, which is the preferred method. If you’re retrofitting old HTML, you can also point to it using the aria-labelledby attribute with the id of the element. Either method will prevent the screen reader from reading it twice: once as alt text, once as on-screen text (see ARIA10 Technique for SC 1.1.1).

GOOD Code Examples

<!-- PREFERRED: The image can be linked to its label via the <figure> and <figcaption> tags -->
<figure>
  <img src="mona-lisa.jpg" alt="">
  <figcaption><em>The Mona Lisa</em>, by Leonardo da Vinci, painted 1503.</figcaption>
</figure>

<!-- If a more descriptive text is only needed for screenreaders, add it to the alt text -->
<figure>
  <img src="mona-lisa-protesters.jpg" alt="A news photograph of environmental protesters splashing tomato sauce across the Mona Lisa's case in January 2024.">
  <figcaption><em>The Mona Lisa</em>'s modern importance and context</figcaption>
</figure>

<!-- Using ARIA, this will show the text below the image and read it to screen readers as well -->
<img src="mona-lisa.jpg" aria-labelledby="mona-lisa-label">
<cite id="mona-lisa-label"><em>The Mona Lisa</em>, by Leonardo da Vinci, painted 1503.</cite>

**Note: For AAA compliance, images of text should never be used except in rare circumstances, like for official logos or font adjustment buttons to toggle bold, italic, and the like. The exceptions are very rare, so if in doubt, don’t use an image of text (see Images of Text: SC 1.4.5, Images of Text (No Exception): SC 1.4.9).

Tables

Tables are one of the most deceptively difficult things to make accessible. If you think about it, the reason tables are so common is that they can show a lot of RELATIONSHIPS in a very small space. Each cell in a table not only has a value, but that value is connected to the relationship between its corresponding row and column. For users with screen readers, that means every cell in the table needs to be programmatically linked to a well-labeled column and row. It should also be clear to the user what the table is describing before diving into its data.

To make a table truly accessible, every table should:

  • have a unique and descriptive label or a caption associated with it (use a <caption>, or an aria-label attribute in the <table> element)
  • when necessary, have a clear description of what the data show (use the aria-describedby attribute in the <table> element)
  • be able to navigate inside the table using arrow keys (done automatically if using correct semantic HTML, like <tr>, <th>, and <td>)
  • have descriptive row and column names (don’t call something “Column 2”, for instance)
  • connect each cell with its corresponding row and column (done with the scope attribute in headers or headers attribute in cells)
  • be as simple as possible (avoid compound groupings of headers where possible and rows always, and never nest tables inside other tables)

The good news is that if you build your tables with the correct semantic HTML elements and attributes, all of the keyboard navigability and screen reader capabilities will be built in. When using a screen reader with the first two tables below, there are minor differences, but each is completely accessible, with each cell clearly labelled and easily navigated.


Table 1: A simple table showing the prices of different styles of pants based on size.
Child: Small Child: Large Youth: Small Youth: Large Women: Small Women: Large Men: Small Men: Large
Pants: Sweats $6.50 $7.50 $9.00 $9.50 $12.00 $12.50 $14.00 $15.00
Pants: Jeans $8.00 $8.50 $10.00 $11.00 $14.00 $15.00 $17.00 $18.00
Pants: Khakis $10.00 $10.50 $13.00 $14.00 $18.00 $20.00 $22.00 $25.00
Pants: Slacks $12.00 $13.00 $15.00 $17.00 $20.00 $22.00 $24.00 $27.00

GOOD Code Example for Table 1

<!-- Simple table with associations made using the scope attribute in each header and row -->
<table>
  <caption>
    Table 1: A simple table showing the prices of different styles of pants based on size.
  </caption>
  <!-- Every header must be a <thead>, with its header cells being <th> elements -->
  <thead>
    <tr>
      <td></td> <!-- Blank cells are okay-->
      <th scope="col">Child: Small</th>
      <th scope="col">Child: Large</th>
      <th scope="col">Youth: Small</th>
      <th scope="col">Youth: Large</th>
      <th scope="col">Women: Small</th>
      <th scope="col">Women: Large</th>
      <th scope="col">Men: Small</th>
      <th scope="col">Men: Large</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <!-- The first cell in each row is also a header cell (<th>), not a data cell (<td>) -->
      <th scope="row">Pants: Sweats</th>
      <td>$6.50</td>
      <td>$7.50</td>
      <td>$9.00</td>
      <td>$9.50</td>
      <td>$12.00</td>
      <td>$12.50</td>
      <td>$14.00</td>
      <td>$15.00</td>
    </tr>
    <tr>
      <!-- The first cell in each row is also a header cell (<th>), not a data cell (<td>) -->
      <th scope="row">Pants: Jeans</th>
      <td>$8.00</td>
      <td>$8.50</td>
      <td>$10.00</td>
      <td>$11.00</td>
      <td>$14.00</td>
      <td>$15.00</td>
      <td>$17.00</td>
      <td>$18.00</td>
    </tr>
    <!-- More rows as needed... -->
  </tbody>

</table>

An equivalent table could be made with compound headers:


Child Youth Women Men
Small Large Small Large Small Large Small Large
Pants Sweats $6.50 $7.50 $9.00 $9.50 $12.00 $12.50 $14.00 $15.00
Jeans $8.00 $8.50 $10.00 $11.00 $14.00 $15.00 $17.00 $18.00
Khakis $10.00 $10.50 $13.00 $14.00 $18.00 $20.00 $22.00 $25.00
Slacks $12.00 $13.00 $15.00 $17.00 $20.00 $22.00 $24.00 $27.00

Despite looking different, the two tables would sound equivalent to users with screenreaders, including the table’s label, which is handled by the aria-label attribute and completely visually hidden in the second example. As an aside, if the label has useful information, it would generally be best to show it in the <caption> rather than visually hide it in an aria-label attribute.

GOOD Code Example for Table 2

<!-- A more complex table with compound headers. Using colgroup and rowgroup, screen readers will combine the header labels together -->
<table aria-label="Table 2: A grouped table showing the prices of different styles of pants based on size.">
  <!-- Every header must be a <thead>, with its header cells being <th> elements -->
  <thead>
    <tr>
      <td colspan="2"></td> <!-- Because of the extra grouping column below, this blank cell is now two columns wide -->
      <!-- colspan designates how many columns this column spans across  -->
      <th scope="colgroup" colspan="2" style="text-align: center;">Child</th>
      <th scope="colgroup" colspan="2" style="text-align: center;">Youth</th>
      <th scope="colgroup" colspan="2"> style="text-align: center;"Women</th>
      <th scope="colgroup" colspan="2" style="text-align: center;">Men</th>
    </tr>
    <tr>
      <td colspan="2"></td>
      <th scope="col">Small</th>
      <th scope="col">Large</th>
      <th scope="col">Small</th>
      <th scope="col">Large</th>
      <th scope="col">Small</th>
      <th scope="col">Large</th>
      <th scope="col">Small</th>
      <th scope="col">Large</th>
    </tr>
  </thead>
  <!-- The tbody is used for the rows filled with data -->
  <tbody>
    <tr>
      <!-- With multi-row <th> elements, they are only needed in the first row they apply to. -->
      <!-- Note: Due to backwards compatibilty issues, row groups are NOT recommended. -->
      <th scope="rowgroup" rowspan="4">Pants</th>
      <th scope="row">Sweats</th>
      <td>$6.50</td>
      <td>$7.50</td>
      <td>$9.00</td>
      <td>$9.50</td>
      <td>$12.00</td>
      <td>$12.50</td>
      <td>$14.00</td>
      <td>$15.00</td>
    </tr>
    <tr>
      <th scope="row">Jeans</th>
      <td>$8.00</td>
      <td>$8.50</td>
      <td>$10.00</td>
      <td>$11.00</td>
      <td>$14.00</td>
      <td>$15.00</td>
      <td>$17.00</td>
      <td>$18.00</td>
    </tr>
    <!-- 2 more rows of data would be needed to justify the rowspan="4" attribute in the <th> with the rowgroup attribute -->
  </tbody>

</table>

Occasionally, it MAY be appropriate to use a more complicated table design to convey information. While this is not recommended, it can still be made accessible if some additional work is put in.


Table 3: A complex table with irregular groups for a conference schedule.
Date Schedule Location Session
Start End
Tuesday, January 23 8:30 AM 10:30 AM S-230C AI Prompt-a-thon with the Microsoft Team
8:30 AM 10:30 AM S-320A AI and Access: Equity and Inclusion in Online Learning
11:00 AM 11:50 AM S-320D That's What ChatGPT Said: Authentic Engagement with Artificial Intelligence
Lunch from 12:00 PM to 12:50 PM
1:00 PM 2:30 PM S-230D AI in Education: Enhancing or Replacing Traditional Support?
1:00 PM 2:30 PM S-320D Why Access Matters in Computer Science: CMU CS Academy

With complex tables, instead of simply assigning relationships to cells using the scope attribute in the column and row headers, you may sometimes need to associate each cell individually using the headers attribute and listing their respective headers’ ids in the order you want them read. This can be tedious by hand, but done fairly quickly if tables are generated using scripts.

GOOD Code Example for Table 3

<table>
  <caption>Table 3: A complex table with irregular groups for a conference schedule.</caption>

  <!-- Every element in the header rows is given an id so it can be referenced-->
  <thead>
    <tr>
      <!-- Notice the odd mix of colspans, rowspans, and both in the headers -->
      <th rowspan="2" id="date">Date</th>
      <th colspan="2" id="schedule">Schedule</th>
      <th rowspan="2" id="location">Location</th>
      <th colspan="2" rowspan="2" id="session">Session</th>
    </tr>
    <tr>
      <th id="start">Start</th>
      <th id="end">End</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <!-- Each <td> element is given specific headers that describe what the cell is referring to -->
      <th id="tuesday" rowspan="6">Tuesday, January 23</th>
      <td headers="schedule start tuesday">8:30 AM</td>
      <td headers="schedule end tuesday">10:30 AM</td>
      <td headers="location tuesday">S-230C</td>
      <td headers="session tuesday">AI Prompt-a-thon with the Microsoft Team</td>
    </tr>
    <tr>
      <td headers="schedule start tuesday">8:30 AM</td>
      <td headers="schedule end tuesday">10:30 AM</td>
      <td headers="location tuesday">S-320A</td>
      <td headers="session tuesday">AI and Access: Equity and Inclusion in Online Learning</td>
    </tr>
    <tr>
      <td headers="schedule start tuesday">11:00 AM</td>
      <td headers="schedule end tuesday">11:50 AM</td>
      <td headers="location tuesday">S-320D</td>
      <td headers="session tuesday">That's What ChatGPT Said: Authentic Engagement with Artificial Intelligence</td>
    </tr>
    <tr>
      <td headers="schedule tuesday" colspan="5">
        <strong><em>Lunch from 12:00 PM to 12:50 PM</em></strong>
      </td>
    </tr>
    <tr>
      <td headers="schedule start tuesday">1:00 PM</td> 
      <td headers="schedule end tuesday">2:30 PM</td>
      <td headers="location tuesday">S-230D</td>
      <td headers="session tuesday">AI in Education: Enhancing or Replacing Traditional Support?</td>
    </tr>
    <tr>
      <td headers="schedule start tuesday">1:00 PM</td>
      <td headers="schedule end tuesday">2:30 PM</td>
      <td headers="location tuesday">S-320D</td>
      <td headers="session tuesday">Why Access Matters in Computer Science: CMU CS Academy</td>
    </tr>
  </tbody>
</table>

Colors

Color cannot ever be the only way that information is communicated. For example, imagine a list of library books that are either available or unavailable, but the only way to tell is if the title of the book is colored green for available and red for unavailable. In this case, users who are blind, colorblind, or using their screen in a high-contrast mode will be unable to tell whether a book is available or not (see Use of Color: SC 1.4.1). Color may be still used to convey information, but in addition the word “Available” should be visible, or an icon of some kind (with alt text). In short, your site should still work in black and white.

Contrast

Text should be easy to read, not just for users with poor vision, but also for users who are in areas with too much or too little light. When you are styling, always check your text colors against all of the background colors they may be placed on. There are good websites where you can check the contrast ratio between foreground and background text, including WhoCanUse (which focuses on color blindness), WebAIM’s Contrast Checker and AccessibilityChecker.org. Generally speaking, the guidelines for standard text in paragraphs are stricter than for large or bold text, which need slightly less color contrast to be easily readable (see Contrast (Minimum): SC 1.4.3, Contrast (Enhanced): SC 1.4.6).

Additionally, user interface components and other graphics that are required to understand context (like state indicators or other icons) also have a minimum contrast requirement (see Non-text Contrast: SC 1.4.11).

Text Size

Most browsers have built-in zoom features, and it’s a good idea to zoom in and out of a web page to see what it looks like at various sizes. While most text will automatically resize or wrap lines in different places as necessary, this can be problematic when you have a button or another element with a fixed size.

Some users have custom software to aid in readability. For instance, they can override your site’s CSS to increase font size, line height, or spacing. You need to ensure that all the text will be visible and readable even when users aren’t following your built-in styles (see Text Spacing: SC 1.4.12). The following example is adapted from W3.org’s WCAG 1.4.12 Failure page:

ORIGINAL Code Example

<div style="font-size:14px; width:120px; height:125px; border: thin solid gray;">
  <p style="padding: 5px; margin: 0px;">Now is the time for all good men to come to the aid of their country.</p>
</div>
<p style="margin-top:0;">The quick brown fox jumps over the lazy dog.</p>

That code would normally render just fine, as shown below:

Now is the time for all good men to come to the aid of their country.

The quick brown fox jumps over the lazy dog.

However, if the user has changed the line spacing, it can lead to a few different visual errors, based on the styling rules of the <div> that has a fixed size:

overflow: initial;

Now is the time for all good men to come to the aid of their country.

The quick brown fox jumps over the lazy dog.

overflow: hidden;

Now is the time for all good men to come to the aid of their country.

The quick brown fox jumps over the lazy dog.

overflow: auto;

Now is the time for all good men to come to the aid of their country.

The quick brown fox jumps over the lazy dog.

When overflow: initial, the text goes outside the box and writes on top of the next element. When overflow: hidden, the last line of the text is cut off completely. And when overflow: auto, the text is technically visible, but has an unsightly scrollbar.

The point is that even with the cleanest layouts, fixed sizes should be avoided. Let containers scale flexibly based on the dynamic size of their content. And never assume that your CSS is the only styling that could be applied to the page.

Videos

Videos involve both audio and video, and should accommodate users who can’t perceive either one. For people who are hearing impaired, captions, transcripts, and/or sign language versions should be available of all videos. For users who are vision impaired, a written audio description or separate audio description version should be available (see Time-based Media: SC 1.2).

Principle 2: Operable

User interface components and navigation must be operable.

  • 2.1 Keyboard Accessible: Make all functionality available from a keyboard.
  • 2.2 Enough Time: Provide users enough time to read and use content.
  • 2.3 Seizures and Physical Reactions: Do not design content in a way that is known to cause seizures or physical reactions.
  • 2.4 Navigable: Provide ways to help users navigate, find content, and determine where they are.
  • 2.5 Input Modalities: Make it easier for users to operate functionality through various inputs beyond keyboard.

People who are blind, as well as those with motor impairments (imagine someone with Parkinson’s disease or MS), may find it impossible to use a mouse. You aren’t responsible for designing or creating whatever device they physically use, but you are responsible to make sure it works for them in expected ways. For instance, foot pedals and joysticks essentially act as keyboard arrow emulators. If you make sure that your site works with arrow-key navigation, it should work for most custom devices.

Specifically, being keyboard navigable means that you can do everything a user can do with a mouse… without a mouse. That means being able to navigate in, out, within, and through any elements on the page (see Keyboard: SC 2.1.1, Keyboard (No Exceptions): SC 2.1.3).

Landmarks and Semantic HTML

In the early days of the web, HTML only had a few different tags, and almost all of them were primarily used to change how a document looked. For example, <br> for line breaks, <p> for paragraph breaks, <b> for bold, and <i> for italic. In HTML5, styling is handled using CSS. There are still a lot of different tags, including <caption>, <dialog>, <footer>, and <section>, but most of them show structure and purpose rather than visual style.

Based on styling rules, the following pages could render identically:

BAD code example:

<div id="header">
  <div class="logo-title">Cool Site Title</div>
</div>
<div id="main-nav">
  <div class="main-nav-link"><a href="home.html">Home</a></div>
  <div class="main-nav-link"><a href="about-us.html">About Us</a></div>
  <div class="main-nav-link"><a href="contact.html">Contact</a></div>
</div>

GOOD code example:

<header>
  <div class="logo-title">Cool Site Title</div>
</header>
<nav aria-label="Main Menu">
  <ul>
    <li><a href="home.html">Home</a></li>
    <li><a href="about-us.html">About Us</a></li>
    <li><a href="contact.html">Contact</a></li>
  </ul>
</nav>

For screen readers, using the right tags is essential. If everything is a <div> there is no way for a screen reader to know where the important parts of the page are. The following tags are essential “landmarks” that screen readers use to navigate and skip directly to important content:

HTML Tag Required Only 1 Per Page ARIA Equivalent Supported by Screen Readers
<header> Yes Yes role=”banner” Yes
<nav> Yes No** role=”navigation” Yes
<main> Yes Yes role=”main” Yes
N/A No No role=”search” Yes
<h1> Yes Yes N/A Yes: Usually with an <h1> shortcut
<h2> - <h6> No No N/A Yes
<aside> No No role=”complementary” Yes
<section> No No role=”region” Mixed
<article> No No role=”article” Mixed
<form> No No role=”form” Mixed: Only if role=”form” present
<footer> Yes Yes role=”contentinfo” Yes

**Multiple <nav>s are allowed, but each should have a distinct aria-label attribute, such as “Main Menu” or “Product Listing”.

When the correct semantic tags are used, screen readers are able to use hotkeys or other shortcuts to go directly to where they want (see Bypass Blocks: SC 2.4.1). That way they don’t have to listen to the entire header and navigation menu on every single page.

Making Elements Tab-able

By default, certain elements on the screen will be focused on when using tab and shift-tab (like input fields and buttons), and others will be skipped. They will also progress in the order that they appear in the HTML code. If you feel that things aren’t moving in a sensible way, you can change the layout with CSS without tampering with the underlying code.

If you need to make an element gain focus with tab that doesn’t already, set tabindex="0" in its attributes. Similarly, to turn it off, set tabindex="-1". Do not use any other values, as this changes the order that things are focused on when tabbing, and almost always leads to confusion for the end user (see Focus Order: SC 2.4.3).

Avoid Keyboard Traps

“Keyboard Traps” are places that the user cannot navigate away from using arrow keys or tab. Users should always be able to press tab to go to the next section, and shift-tab to go backwards. Poorly designed sites can lock a user in a menu, navigation element, or popup with no way to get out that doesn’t require using a mouse (see No Keyboard Trap: SC 2.1.2).

Make the Focus Visible

When a user is moving around with tab, the element they are currently on has focus. This is usually the equivalent of clicking on an input field or other place that the user can then interact with, but NOT the same as clicking on a button. There must always be a visible indication that an object has focus so users can know where they are on the screen (see Focus Visible: SC 2.4.7:). This is typically done in CSS using the :focus condition.

button {
  font-weight: normal;
  border: 3px solid $button-border-normal;

  &:focus {
    font-weight: bold;
    border: 3px solid $button-border-focus;
  }
}

input {
  border: 1px solid $dark-gray;

  &:focus {
    border: 1px solid $brand-color;
    box-shadow: 0px 0px 3px $input-focus;
  }
}

Notice that in both these cases, when you want something to look different based on state, you can’t do that with just color alone. There needs to be something else, like a border or dropshadow around an element or changing text to bold. Generally, a focus border needs to be at least 2 pixels thick with a 3:1 change-of-contrast ratio (see Focus Appearance: SC 2.4.13).

Don’t Lose Focus

Just focusing on a component should NOT change anything about the site context. This shouldn’t open a new page, submit a form, or trigger any other function. It should be assumed that when an element has been focused on, that it is being navigated past, and hasn’t really been interacted with. If you have any scripts that trigger on focus, you should consider changing them to trigger on activate (see On Focus: SC 3.2.1).

Let’s say that you have a button on your site or app that, when clicked (activated), opens up a required confirmation popup. Screen readers won’t necessarily capture that, and other keyboard users may get stuck with their focus on an object in the background. If this kind of interaction takes place, you need to be sure to:

  1. Manually change the focus (using JavaScript) to the new element, like the title or first line of the popup.
  2. Manually change other elements that are in the background to inactive so they can’t be tabbed to.
  3. Make sure there is a keyboard-navigable way to leave the popup (a “confirm” or “exit” button).
  4. When the popup closes, manually restore focus to wherever it had been before (or another logical place), so the user doesn’t have to navigate all the way back to it.

Don’t Create Your Own Shortcuts

Screen readers have their own handy shortcuts, like tapping h to skip to the next heading element or m to skip to the main content. If you implement any custom keyboard shortcuts that use letters, numbers, and symbols (as opposed to two-key shortcuts, like control + i), that can mess up what screen readers are trying to do, and you are required to have some sort of accessible mechanism to remap and customize those controls (see Character Key Shortcuts: SC 2.1.4). It is preferable to avoid them.

Give Users Enough Time

If there is any element of your site or app that has an action that the user has to complete within a given time, like a page timeout, the user must be able to turn it off or at least adjust or extend it (see Timing Adjustable: SC 2.2.1, No Timing: SC 2.2.3). If their session expires, they should be warned beforehand and be able to re-authenticate without losing any data (see Re-authenticating: SC 2.2.5, Timeouts: SC 2.2.6).

If you have information that scrolls or autoupdates, there must be some mechanism for a user to pause, stop, or hide it (see Pause, Stop, Hide: SC 2.2.2).

Principle 3: Understandable

Information and the operation of the user interface must be understandable.

  • 3.1 Readable: Make text content readable and understandable.
  • 3.2 Predictable: Make Web pages appear and operate in predictable ways.
  • 3.3 Input Assistance: Help users avoid and correct mistakes.

Screen readers help users navigate by automatically creating lists of different types. They can select different browser tabs using a list of tab titles, or generate a list of clickable links on a page. Since these list are created by screen readers on the fly, it’s important that the items on the list are distinct, accurate, and have the most important information first. This applies to page titles, headings, labels, and links.

Page Titles

Rule 1: Titles should be accurate and descriptive.

Rule 2: Important or distinct information should come first.

Every webpage absolutely must have a title. The <title> element appears in the <head> of the HTML document and instead of appearing on the page, appears in the web browser’s tab. The following image is an example of tabs that are accurate, but unfriendly to screen readers.

Image of a row of tabs that are all different but all start with the words "Amazon.com."

If a user with a screen reader attempted to switch tabs, they would have to go through the following list:

  • Amazon.com. Spend less. Smile more.
  • Amazon.com: Amazon Basics
  • Amazon.com: Electronics
  • Amazon.com: air pods

Each time, they would be forced to listen to the words “Amazon dot com” over and over before being able to tell if they were on the right tab and should enter it. A much better series of titles for the same pages would be:

  • Amazon.com Home Page. Spend less. Smile more.
  • Amazon Basics - Amazon.com
  • Electronics - Amazon.com
  • “Air Pods” Search Results - Amazon.com

In each case, the most important information comes first, and allows for faster navigation. As an added benefit, this arrangement also helps sighted users, since when a lot of tabs are open, the titles get shorter and shorter. At some point, the text would get cut off and all a user would be able to read is “Amazon.com…”, “Amazon.com…”, “Amazon.com…”, “Amazon.com…”. They would then have to open and repoen multiple tabs to figure out what is on each one. As a good rule of thumb, if your website had breadcrumbs, your page title should contain the same information, but in the reverse order (see Page Titled: SC 2.4.2).

Headings

Rule 1: Headings should be accurate and descriptive.

Rule 2: Ideally, a user should be able to navigate the page using only headings.

Even if your webpage or app is not a long or descriptive article, you should use headings (<h1> - <h6>) to outline important sections, subsections, and other areas. For instance, if you have a callout box with important or actionable information inside, the box itself is not visible to screen readers. The main title or sentence in the box should be a heading, like <h2> or <h3>, so screen readers can navigate directly to it (see Headings and Labels: SC 2.4.6).

Headings don’t need to be lengthy if they follow a standard format. For example, a well-titled academic paper might have generic headings, like “Abstract”, “Methodology”, “Data”, “Analysis”, and “Conclusion”, which would all be descriptive enough to give a reader (using a screen reader or not) plenty of guidance.

Labels

Rule 1: Labels should be clear and unambiguous.

Rule 2: Labels must be programmatically linked to the correct element.

Rule 3: Labels should always be visible.

Generally speaking, labels for input fields and forms should be boring. This is not the moment to let your creativity shine. An text field for a user to enter their first name shoulo have the label “First Name” attached to it using the aria-labelledby attribute (see Info and Relationships: SC 1.3.1, Headings and Labels: SC 2.4.6). When a screen reader focuses on the input element, the attached label will be read aloud. For example:

<form>
  <!-- The input text field receives its spoken label by referencing the "first-name-label" id-->
  <label id="first-name-label">First Name</label>
  <input name="first-name-text" type="text" aria-labelledby="first-name-label">

  <!-- This example grabs the label from an adjacent button, again using an id-->
  <input name="search-text" type="text" aria-labelledby="search-button">
  <input name="search-button" id="search-button" type="submit" value="Search">
</form>

This code would render the following:


For more complicated examples, see the WCAG aria-labelled technique page.

Make sure that labels don’t disappear. For example, using the placeholder text as a label is inappropriate, since as soon as a user types in the field, the placeholder is no longer visible (see Labels or Instructions: SC 3.3.2).

Rule 1: Links should be accurate and descriptive.

Rule 2: A user should be able to tell what the link will do just from the link text.

Screen readers and other assistive technologies can automatically generate a list of clickable links available in a document. The label for each link is automatically generated from the link text or an associated label (see Link Purpose (Link Only): SC 2.4.9). The following are both acceptible ways to generate helpful labels:

<!-- With headlines, the link text itself is properly descriptive. -->
<h3><a href="taxhike.html">City managers propose 75% increase in property taxes for the coming year</a>.</h3>

<!-- If you ever use a "Read more" or "Click here" link, it must have an explicit aria-label attribute. -->
<p>City managers are proposing a 75% increase in property taxes for the coming year. <a href="taxhike.html" aria-label="Read about the proposed 75% property tax increase">Read more...</a></p>

Languages

Screen readers and page translators rely on the lang attribute to know what language a page is written in. For screen readers, in particular, this is important, since screen readers will pronounce things in different ways based on the perceived language.

The lang attribute should always be set in the <html> tag on the page, as well as any individual elements where the language changes (see Language of Page: SC 3.1.1, Language of Parts: SC 3.1.2). For example:

<html lang="en">
<head>...</head>
<body>
  ...
  <p>When visiting Puerto Rico, I wasn't surprised to be greeted more with <span lang="es">"buenos días"</span> than "good morning."</p>
  ...
</body>
</html>

The lang attribute only accepts certain specific values, such as “en” for English, “es” for Spanish, “de” for German, “fr” for French, “pt” for Portuguese, and “zh” for Chinese. See the Language Code Reference Page for a complete list if you have elements of your site or app that are in other languages.

Principle 4: Robust

Content must be robust enough that it can be interpreted by a wide variety of user agents, including assistive technologies.

  • 4.1 Compatible: Maximize compatibility with current and future user agents, including assistive technologies.

The principle of robusness is to maximize compatibility with current (and future) browsers, systems, and assistive technologies. While reading simple text is a solved problem, you should be extra careful with interface components, which include but are not limited to:

  • form elements
  • links
  • buttons
  • other interactive components
  • components generated by scripts

When using these “rich” components, you should ahere closely to the ARIA guidelines, which are explained on the ARIA Deep Dive page.

  • Tables: Use <th> elements with ‘scope=”col”’ attribute to make sure that screen readers can parse them intelligibly.
  • PDFs: Export PDF documents as “Tagged PDF” which includes HTML-style tags to assist screen readers.

HTML Boilerplate Example

<!DOCTYPE html>
<html lang="en">  <!-- 3.1.1: Language of Page -->
  <head>
    <title>Accessible HTML Boilerplate</title>  <!-- 2.4.2: Page Titled -->
    <!-- Additional <link> elements for importing CSS and Javascript files go here-->
  </head>
  <body>
    <!-- The header and nav elements should be consistent across the site or app -->
    <header>

    </header>
    <nav>
      <ul>
        <li>Home</li>
        <li>About Us</li>
        <li>Contact</li>
      </ul>
    </nav>

    <!-- Screen readers can skip over the <header> and <nav> to go straight to <main> or <h1>-->
    <main>
      <!-- There should only ever be one <h1> per page, which should be equivalent to the <title> -->
      <h1>Accessible HTML Boilerplate</h1>
      <p>Lorem ipsum...</p>
      <h2>Subheader Level 2</h2>
    </main>

  </body>

</html>

@INCOMPLETE