How to fix WCAG 1.3.1 info and relationships

WCAG 2.2 Success Criterion 1.3.1 requires that information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. The normative text reads:

“Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.”

WCAG 2.2 SC 1.3.1 (W3C)

In plain language: if you communicate something visually (a heading, a list, a column relationship), the same meaning must reach a screen reader, a search engine, and a future redesign without depending on CSS surviving.

The failing patterns

<!-- "headings" that are just bold paragraphs -->
<p style="font-weight: 700; font-size: 1.25rem;">Frequently asked</p>

<!-- "lists" assembled from <br> tags -->
WCAG 2.2 covers:<br>
- Perceivable<br>
- Operable<br>
- Understandable<br>
- Robust<br>

<!-- table layout for tabular data without <th> or scope -->
<table>
  <tr><td>Name</td><td>Role</td></tr>
  <tr><td>Maria</td><td>Engineer</td></tr>
</table>

<!-- everything wrapped in <div> -->
<div class="header">...</div>
<div class="nav">...</div>
<div class="main">...</div>

A sighted user perceives structure from typography, spacing, and alignment. A screen-reader user, a Reader Mode parser, a search-engine crawler, and a print stylesheet all consume the DOM. If the DOM is structureless, they cannot reconstruct meaning.

The fix — encode structure in markup

<!-- real headings -->
<h2>Frequently asked</h2>

<!-- real list -->
<p>WCAG 2.2 covers:</p>
<ul>
  <li>Perceivable</li>
  <li>Operable</li>
  <li>Understandable</li>
  <li>Robust</li>
</ul>

<!-- real table with header cells -->
<table>
  <caption>Editorial team</caption>
  <thead>
    <tr><th scope="col">Name</th><th scope="col">Role</th></tr>
  </thead>
  <tbody>
    <tr><td>Maria</td><td>Engineer</td></tr>
  </tbody>
</table>

<!-- landmarks -->
<header>...</header>
<nav aria-label="Primary">...</nav>
<main id="main">...</main>
<aside aria-label="Sponsorship">...</aside>
<footer>...</footer>

Landmarks let screen-reader users jump between regions with a single key. NVDA’s D and VoiceOver’s rotor both surface landmarks first.

Heading order

<h1> once per page; never skip a level. A page with <h1><h2><h4> reads “level four heading” with no context for the missing three. axe-core’s heading-order rule catches the most common cases.

<h1>Article title</h1>
<h2>Background</h2>
<h3>Earlier work</h3>     <!-- valid: h2 then h3 -->
<h2>Method</h2>
<h2>Results</h2>
<h3>Tables</h3>
<h4>Per-region</h4>       <!-- valid: h3 then h4 -->

For component libraries, expose a level prop:

function Section({ level = 2, children, title }) {
  const Heading = `h${Math.max(2, Math.min(6, level))}`;
  return (
    <section>
      <Heading>{title}</Heading>
      {children}
    </section>
  );
}

Lists

If two or more items share semantic relationship (siblings of equal weight, ordered steps, definitions), use <ul>, <ol>, or <dl>. Visual bullet points in CSS without underlying list markup are invisible to screen readers; conversely, screen readers count list items, so a list of one should usually be a paragraph.

Tables

<th scope="col"> and <th scope="row"> make the relationship between data and header explicit. Avoid <table> for layout — CSS Grid is universally supported and exposes no false semantics.

For tables with merged headers, use <th scope="colgroup"> and headers="..." attributes; or, when complexity grows, refactor into multiple simpler tables.

Forms

Form controls require explicit labels. <label for> is the cleanest mechanism. aria-labelledby works for non-form widgets. Grouping related fields uses <fieldset> and <legend> — for example, a “shipping address” block.

How to test

Automated — the following axe-core rules cover most 1.3.1 failures:

  • heading-order
  • landmark-one-main, region, landmark-banner-is-top-level
  • list, listitem
  • table-fake-caption, td-headers-attr, scope-attr-valid
  • label, form-field-multiple-labels
  • definition-list

Manual screen-reader landmark navigation — NVDA’s D key cycles through landmarks. VoiceOver’s rotor — VO+U — offers the same view. A page with a single landmark tagged main and no others is harder to navigate than one with header, nav, main, aside, footer.

When this is hard

  • Component libraries that wrap children in <div>. Some libraries emit <div> wrappers around your <h2>. This usually does not break semantics but can break heading order if the wrapper is itself rendered inside an unexpected context.
  • CMS-authored content. Authors paste from Word and produce styled paragraphs that look like headings. Configure the editor to expose heading levels as toolbar buttons; reject content with no <h2>.
  • SPAs and route changes. When the route changes, screen readers do not automatically announce the new page. Move focus to the new <h1> on route change, or announce the page title via aria-live on a dedicated container.

A useful self-test: hide all CSS (DevTools “Disable styles” or Reader Mode), and read the page top-to-bottom. If the result is intelligible, your 1.3.1 posture is sound. If it reads as one wall of text, your meaning lives in CSS, and a screen-reader user is missing it.