A screen reader speaks a page aloud for blind and low-vision users. A crawler is the program a search engine uses to fetch pages. Both work from the page's structure and markup, not from how it looks. Neither is fooled by a div that merely looks like a heading, because the HTML is where the page says what it is.
This is why accessibility work and SEO work overlap without either side naming it. Some fixes help both. Others help only people, and this post says which is which. Break either kind and nothing warns you, because both live in the layer a build never checks. A build is the automated step that compiles your code and checks it before a deploy.
The checks below start with the ones that help both.
Heading order
A screen reader user commonly navigates a page by jumping between headings, skipping the prose entirely. Google also reads headings as a hint about what a page covers, but it does not care whether the levels are tidy, so the order rules below are mainly for people. Either way, the order has to be right in the markup, not just look right on screen.
<!-- skipped level: h2 jumps straight to h4 -->
<h1>Accessibility and search engines</h1>
<h2>Heading order</h2>
<h4>Skipped levels</h4>
<!-- correct: every level follows the one above it -->
<h1>Accessibility and search engines</h1>
<h2>Heading order</h2>
<h3>Skipped levels</h3>One h1 per page. Several h1 tags can blur the starting point for a screen reader user who jumps to the top of the page. Search engines cope with more than one, so this is mainly an accessibility rule.
<!-- avoid: two h1 tags -->
<h1>Site name</h1>
<h1>Accessibility and search engines</h1>
<!-- use: one h1, with the site name as plain text -->
<p class="site-name">Site name</p>
<h1>Accessibility and search engines</h1>Do not skip a level for visual size. Wanting a smaller heading is legitimate, but the fix is CSS, not skipping from h2 straight to h4. Skipping breaks the heading outline, the nested structure a screen reader user navigates by. Google does not penalize skipped levels, so this one helps people rather than rankings.
h2.compact {
font-size: 0.875rem;
}Semantic elements over generic divs
A <button> is focusable, triggers on Enter and Space, and announces itself as a button to a screen reader without any extra work. A <div onclick> does none of that by default. Search engines make a similar distinction: a <nav>, a <main>, and an <article> may give them slightly clearer clues about what each region of the page is for. A page built entirely from <div> tags with visual styling gives search engines much less to go on.
Interactive elements should be <button> or <a>, never a styled <div> with a click handler bolted on.
<!-- avoid -->
<div onclick="openMenu()">Menu</div>
<!-- use -->
<button type="button" onclick="openMenu()">Menu</button>Page regions should use <nav>, <main>, <header>, and <footer> instead of <div id="nav">. A screen reader can jump directly to main; a crawler can use the tags as a hint to tell the navigation apart from the content.
<nav>...</nav>
<main>...</main>
<footer>...</footer>Lists should use <ul> or <ol>, not lines of text with dashes. A screen reader announces something like "list, 3 items" and lets the user skip the whole group.
<!-- avoid -->
<p>- Home<br>- About<br>- Work</p>
<!-- use -->
<ul>
<li>Home</li>
<li>About</li>
<li>Work</li>
</ul>Data tables should use <th> and <caption>. Header cells let a screen reader announce which column a value belongs to. Use tables for data, not for page layout.
<table>
<caption>Audit scores by page</caption>
<thead>
<tr><th scope="col">Page</th><th scope="col">Score</th></tr>
</thead>
<tbody>
<tr><td>Home</td><td>98</td></tr>
</tbody>
</table>Of these, only the page regions are a plausible search hint. Buttons, lists, and table headers are for people.
Link text
A screen reader can pull every link out of a page and read them as one list, so each link has to make sense with nothing around it. Six links that all say "click here" become six identical entries. Google reads the same text, called anchor text, as a description of the page the link points to, so a vague label wastes that signal too.
Say where the link goes. Use words that describe the destination, not the act of clicking.
<!-- avoid -->
<a href="/writing/canonical-urls/">Click here</a>
<!-- use -->
<a href="/writing/canonical-urls/">Canonical URLs guide</a>Repeated "Read more" links need the destination in the name a screen reader announces. A card list where every link says "Read more" gives a screen reader user no way to tell them apart. This fix helps screen reader users only. Search engines read the visible link text.
<a href="/writing/canonical-urls/" aria-label="Read more: Canonical URLs">Read more</a>An image inside a link uses its alt text as the link text. Google also treats that alt text as anchor text.
<a href="/"><img src="logo.svg" alt="Nitish Kumar, home"></a>Page title and language
The <title> is usually the first thing a screen reader announces when a page loads, and it is the headline Google usually shows in search results, though Google sometimes rewrites it. One line does both jobs, and a missing or repeated title leaves both with nothing to tell one page from another.
Give every page its own title. Write one that says what this page covers, not just the site name.
<!-- avoid: same title on every page -->
<title>My Site</title>
<!-- use -->
<title>Canonical URLs guide | My Site</title>The lang attribute on the <html> tag tells a screen reader which language to pronounce the text in. Google works out the language from the visible text, so lang helps people rather than rankings, but it costs nothing to set.
Set lang on the page, and on any passage in another language. Without it, a screen reader may read French text with English pronunciation.
<html lang="en">
<p>The phrase <span lang="fr">mise en place</span> comes from cooking.</p>Alt text and labels
Every image needs a text alternative, and this is one of the clearest overlaps between accessibility and search. A screen reader announces alt text exactly as written. When the alt attribute is missing, many screen readers fall back to reading the file name, which tells the user nothing. An icon-only button with no label is announced only as "button", with no hint of what it does. The search side of the same attribute is covered in how search engines read images and other media.
Images need alt text that says what the image shows. A purely decorative image needs an empty alt="" so a screen reader skips it.
<img src="dashboard.png" alt="Dashboard showing page speed scores">
<img src="divider.png" alt="">Icon-only buttons need aria-label, an attribute that sets the name a screen reader announces, because there is no visible text to read. This one helps assistive technology, not search.
<button type="button" aria-label="Close dialog">
<svg aria-hidden="true">...</svg>
</button>Decorative icons next to visible text need aria-hidden="true" so a screen reader does not announce the icon and the label separately for the same thing.
<button type="button">
<svg aria-hidden="true">...</svg>
Search
</button>Content that needs JavaScript
This one is the reverse of the sections after it: it matters to crawlers, while a screen reader works from the rendered page and is unaffected. A page that arrives as an empty shell and fills itself in with a script is a risk for search engines. Google can run JavaScript, but rendering is queued and can happen later than the first crawl, and other crawlers often do not run it at all. Text that exists in the HTML from the start reaches every user and crawler the same way.
To check, fetch the raw HTML and search for a sentence from the page. If the command prints nothing and the page did load, the sentence is probably not in the HTML and depends on a script. Use a short phrase in plain letters, since punctuation is often encoded.
curl -sSL https://example.com/ | grep "a sentence from the page"Accessibility only
The next four sections (focus states, form labels, ARIA, and color contrast) have little to no effect on search. A page that gets them wrong can lock out the people using it, and none of it shows up in a crawl report.
Focus states
A keyboard user tabs through a page one interactive element at a time. The focus ring, the outline a browser draws around the focused element, is the only way to see where they are. outline: none with nothing put in its place removes that signal entirely.
a:focus-visible, button:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}Removing the outline on :focus and forgetting to restore it on :focus-visible is a common way this breaks. Test it by tabbing through the page without touching the mouse.
A skip link is a normal link at the very top of the page that jumps to the main content, usually hidden until it receives focus. Without one, a keyboard user has to tab through the whole navigation on every page before reaching the content.
<a href="#main-content" class="skip-link">Skip to main content</a>
...
<main id="main-content">...</main>.skip-link { position: absolute; left: -9999px; }
.skip-link:focus { left: 1rem; top: 1rem; }Form labels
Search engines take little interest in forms, so this is almost entirely an accessibility concern. It is also where missing markup hurts users most. A placeholder disappears the moment someone types, so it cannot serve as the only label.
Every input needs a <label> tied to it. Clicking the label focuses the input, and a screen reader announces the label when the field gets focus.
<label for="email">Email address</label>
<input id="email" type="email">Group related controls with fieldset and legend. Without the legend, a set of radio buttons reads as unrelated options.
<fieldset>
<legend>Contact preference</legend>
<label><input type="radio" name="contact" value="email"> Email</label>
<label><input type="radio" name="contact" value="phone"> Phone</label>
</fieldset>Tie error messages to their field. Otherwise a screen reader user may never hear them.
<input id="email" type="email" aria-describedby="email-error">
<p id="email-error">Enter an address like [email protected]</p>ARIA
ARIA (Accessible Rich Internet Applications) attributes add meaning that plain HTML cannot express. The first rule of ARIA is not to use it when a native element already does the job, because wrong ARIA is worse than none: it tells a screen reader something false about the page.
Prefer the native element. A div with a role still needs Enter and Space key handlers and its own focus styling written by hand.
<!-- avoid -->
<div role="button" tabindex="0">Save</div>
<!-- use -->
<button type="button">Save</button>Never hide a focusable element with aria-hidden. Keyboard focus can still land on it, and a screen reader may announce nothing.
<!-- avoid: focusable but hidden from screen readers -->
<button type="button" aria-hidden="true">Save</button>Use aria-expanded on anything that opens and closes. It tells a screen reader user whether the menu is open or closed. A script sets it to true when the menu opens.
<!-- closed state -->
<button type="button" aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>...</ul>Color contrast
Level AA of the Web Content Accessibility Guidelines (WCAG) 2.2 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text. Large means 18 point or bigger, or 14 point bold (about 24 pixels, or about 19 pixels bold). Low-contrast text trips the color-contrast check in Lighthouse, the audit tool built into Chrome DevTools, under its Accessibility category.
/* fails AA on a white background */
color: #a8a8a8;
/* passes AA on a white background */
color: #595959;Color alone must not carry meaning either. A red border as the only sign of a form error may go unnoticed by a color-blind user, so the message has to say it too.
<!-- avoid: only the color says it failed -->
<input style="border: 1px solid red">
<!-- use: the message says it too -->
<input aria-invalid="true" aria-describedby="err" style="border: 1px solid red">
<p id="err">Enter a valid email address.</p>Low-vision users also depend on zoom. A viewport tag with user-scalable=no can block pinch zoom in some browsers, and the meta tags for SEO post explains why to avoid it.
Where to go next
This post covers the parts of accessibility that show up in markup, and which of them matter to search. The full standard is much larger. Where to look next:
- WCAG 2.2 specification: the source for every requirement.
- How to Meet WCAG: the same requirements as a checklist you can filter by level.
- ARIA Authoring Practices Guide: working keyboard behavior for menus, tabs, and dialogs.
- web.dev Learn Accessibility: a gentler starting point.
- WebAIM contrast checker: tests a color pair against the ratios above.
What the build process will not tell you
Nothing in the build fails by default for a missing aria-label. No error appears for a skipped heading level or a focus outline set to none. The page renders, looks correct to a sighted mouse user, and passes every check the build runs. It still fails silently for keyboard and screen reader users, and for a crawler wherever the markup is what it reads.
Lighthouse catches some of this in the same report as Performance and SEO. Keyboard and screen reader testing by hand catches the rest. Neither happens by itself on a deploy. Someone has to open Lighthouse or add it to the automated deploy steps, and until then nothing reports a regression.