1.3.1 Level A Guideline 1.3 Adaptable Added in WCAG 2.0 Partly detectable automatically

Info and Relationships

Structure and relationships conveyed visually are also available programmatically. Headings are headings, lists are lists, tables have header cells, and form fields have real labels.

Who this affects

Screen reader users navigate by structure — jumping heading to heading, listing landmarks, reading a table cell with its row and column headers. Bold text styled to look like a heading provides none of that.

The failure and the fix

Each example below is a self-contained page, loaded in a frame so its markup cannot affect this page. Open either one on its own to test it with a keyboard or screen reader.

Fails 1.3.1

Open the failing example in a new tab

fail.html — the problem

<!-- FAILURE 1: styled to look like a heading, but it is a plain div.
     A screen reader user pressing H to jump between headings finds nothing. -->
<div class="looks-like-heading">Opening hours</div>

<!-- FAILURE 2: a list to the eye, unrelated divs to assistive technology.
     Screen readers announce "list with 3 items" - here they announce nothing. -->
<div class="looks-like-list">
  <div>Monday to Friday, 9am to 6pm</div>
  <div>Saturday, 10am to 4pm</div>
  <div>Sunday, closed</div>
</div>

<div class="looks-like-heading">Stock by branch</div>

<!-- FAILURE 3: a data table with no header cells. Every cell is a td, so a
     screen reader cannot say which column or row a value belongs to.
     Reading cell "12" tells the user nothing at all. -->
<table>
  <tr>
    <td><strong>Branch</strong></td>
    <td><strong>Hardback</strong></td>
    <td><strong>Paperback</strong></td>
  </tr>
  <tr><td>Riverside</td><td>12</td><td>34</td></tr>
  <tr><td>Old Town</td><td>4</td><td>19</td></tr>
</table>

<div class="looks-like-heading">Join the mailing list</div>

<form>
  <!-- FAILURE 4: the label is just text sitting next to the input. There is no
       programmatic association, so the field announces as "edit, blank". -->
  <div class="field">
    Email address
    <input type="email" name="email">
  </div>

  <!-- FAILURE 5: a radio group with no fieldset and no legend. Each option
       announces its own label, but the question they answer is invisible to
       assistive technology. -->
  <div class="field">
    <div class="looks-like-heading">How often?</div>
    <input type="radio" id="f-weekly" name="freq" value="weekly">
    <label for="f-weekly">Weekly</label>
    <input type="radio" id="f-monthly" name="freq" value="monthly">
    <label for="f-monthly">Monthly</label>
  </div>

  <button type="button" class="button">Subscribe</button>
</form>

Meets 1.3.1

Open the passing example in a new tab

pass.html — the fix

<!-- FIX 1: a real heading. It looks the same, and now it is in the heading
     outline, reachable with the H key and listed in the rotor. -->
<h2>Opening hours</h2>

<!-- FIX 2: a real list. Assistive technology announces "list with 3 items",
     which tells the user how much there is before they commit to reading it. -->
<ul>
  <li>Monday to Friday, 9am to 6pm</li>
  <li>Saturday, 10am to 4pm</li>
  <li>Sunday, closed</li>
</ul>

<h2>Stock by branch</h2>

<!-- FIX 3: real header cells with an explicit scope. Now a screen reader reads
     "Old Town, Paperback, 19" instead of a bare "19". The caption names the
     table so it can be found in a list of tables. -->
<table>
  <caption>Copies in stock by branch and format</caption>
  <thead>
    <tr>
      <th scope="col">Branch</th>
      <th scope="col">Hardback</th>
      <th scope="col">Paperback</th>
    </tr>
  </thead>
  <tbody>
    <tr><th scope="row">Riverside</th><td>12</td><td>34</td></tr>
    <tr><th scope="row">Old Town</th><td>4</td><td>19</td></tr>
  </tbody>
</table>

<h2>Join the mailing list</h2>

<form>
  <!-- FIX 4: label's "for" matches the input's "id", so the field announces as
       "Email address, edit". Clicking the label also focuses the field. -->
  <div class="field">
    <label for="email">Email address</label>
    <input type="email" id="email" name="email" autocomplete="email">
  </div>

  <!-- FIX 5: fieldset and legend bind the question to its options, so each
       radio announces as "How often? Weekly, radio button, 1 of 2". -->
  <fieldset class="field">
    <legend>How often?</legend>
    <div>
      <input type="radio" id="p-weekly" name="freq" value="weekly">
      <label for="p-weekly">Weekly</label>
    </div>
    <div>
      <input type="radio" id="p-monthly" name="freq" value="monthly">
      <label for="p-monthly">Monthly</label>
    </div>
  </fieldset>

  <button type="submit" class="button">Subscribe</button>
</form>

How to test it

Turn off CSS, or view the accessibility tree. Every visual grouping, heading level, list, and table relationship should still be expressed in the markup. Check label/for pairings, th with scope, fieldset/legend for radio groups.

  • Automated

    Scanners catch missing form labels, empty headings, and layout tables with headers. They cannot detect a div styled to look like an h2, or a heading hierarchy that is technically valid but semantically wrong.

    Relevant axe rules:

    • label
    • form-field-multiple-labels
    • th-has-data-cells
    • td-headers-attr
    • empty-heading
    • definition-list
    • list
    • listitem
    • aria-required-children
    • aria-required-parent
  • Keyboard

    Unplug the mouse. Move through the page with Tab, Shift+Tab, Enter, Space and the arrow keys. See the keyboard testing script.

  • Screen reader

    Listen to both examples with NVDA, JAWS, VoiceOver or TalkBack and compare what is announced. See the screen reader cheat sheets.

  • Visual

    Zoom to 200% and 400%, narrow the viewport to 320 px, and apply the text-spacing overrides. See the visual testing procedures.

In the specification