Throughout my career, I’ve alternated roles between designer and front-end developer. Most of the time, when I designed something, I wrote most of the front-end code needed to implement it. Back in the early 2000s, if you were a web designer, that was often just what you did.
Coding my own designs was fun, as it allowed me to fulfill both my creative and technical sides. It also felt good knowing the design would be implemented properly and the code would be clean because it was something I did. I’d run the HTML through my validation tools. Then I’d run it through one of my accessibility checkers. No Web Content Accessibility Guidelines (WCAG) errors. No Section 508 errors. That work was DONE. It was clean; it was accessible. Right?
Well, not quite. Automated tools will only catch 25-33% of the accessibility issues on your page. Essentially, these tools will tell you whether your code contains bugs, but the other 70 percent relates to user experience (UX) issues. Left unchecked, users will be frustrated; at worst, they will not be able to access critical information at all.
The only way to address the other 70 percent is through manual testing. When I started trying to use my coded designs either with a keyboard or a screen reader, I was shocked by how much I had overlooked. Then came the anxiety freight train of knowing how much work I put out there that I never manually tested.
One such aspect I hadn’t fully considered until I began using a screen reader was scannability as part of my testing. I’ve always (mostly) written semantic HTML, used correct headings, landmarks, etc. It was simply something I did “for screen readers,” but never quite learned how any of it translated to how users scan a page on screen readers. Scanning is such a basic thing to do when visiting a page, it’s practically a reflex. So when I finally learned the screen reader’s equivalent, it made so many other things click, particularly how I think about UX with assistive technology.
Scanning the Page with Screen Readers
Scanning allows us to consume a lot of high-level information that helps us determine where important information is located or whether or not a page is worth investing our full time and attention. It is a swift, yet essential task we perform almost instinctively. But how might someone using a screen reader do the equivalent?
It is possible! Screen readers don’t just read from top to bottom, requiring users to read through every single piece of content on the page. They have special navigational tools such as a rotor feature or headings menu, depending on the screen reader. In this post, we will be using VoiceOver on MacOS, so we will be talking about the rotor menu (with VoiceOver running, press Control + Option + U to open the rotor menu - more VoiceOver shortcuts) and using examples as announced by VoiceOver. But all screen readers have something equivalent that allows users to scan the page.
The rotor is not just for perusal; it can be used for navigation as well.

Rotor menu enabled on Coforma.io
HTML code generates the contents of the rotor menu. Well-written, semantic HTML can make the rotor menu usable. Poorly written, unsemantic HTML can make the rotor menu confusing or useless.
The rotor menu contains several key sections, such as headings, links, landmarks, and form controls, and can be configured to the user's preferences. To control the rotor menu, use the left and right arrow keys to move between sections, the up and down arrow keys to select a list item, and the enter key to go to that element.
Landmarks
Landmarks allow users to open the rotor to find and jump to key parts of the page, such as the main content or navigation menus. Accessible Rich Internet Applications (ARIA) landmark roles are important sections of a webpage that users often need to navigate directly to, such as the main content or a navigation menu. The landmark roles are:
banner
: Includes prime heading or internal page title. Indicated withrole="banner"
or<header>
complementary
: Supports the main content but can be separate and meaningful on its own. Indicated withrole="complementary"
or<aside>
contentinfo
: Info about the parent document, usually containing links to privacy policy and copyright. Indicated withrole="contentinfo"
or<footer>
Form
: Contains form-associated elements. Indicated withrole="form"
or<form>
, but does not appear in the rotormain
: Contains the main content of the document. Indicated withrole="main"
or<main>
navigation
: Contains navigational links within or related to the document. Indicated withrole="navigation"
or<nav>
region
: Content relevant to an author-specified purpose and important enough that users will want to navigate to specifically. Indicated using <section> with an accessible name provided byaria-labelledby
,aria-label
, ortitle
search
: Contains a search tool. Indicated byrole="search"
Where some landmarks begin and end might seem obvious from their visual design, but developers should work with designers on exact designations. Placing main content outside the main landmark might cause users to miss important content, while placing content that isn’t main content inside the landmark could degrade the user experience because the user would have read through irrelevant or repetitive content.
It is also possible to have more than one instance of a landmark role on a page. For example, it is common to have multiple navigational landmarks. You can provide helpful context inside the rotor menu by using aria-label
or aria-labelledby
(we don’t recommend using title to name landmarks). Using aria-labelledby
is preferable because it references another HTML element, allowing the translation plugins to translate the content.
When there are multiple instances of a navigation landmark, they will both display in the rotor menu as “Navigation,” making it impossible to distinguish one from another. A heading element inside the landmark will give context to the content underneath it when reading the page, but it will not name the landmark in the rotor.
<nav>
<h3 class="screen-reader-only">Main</h3>
<ul>
<li><a href="#">Nav 1</a></li>
<li><a href="#">Nav 2</a></li>
...
</ul>
</nav>
<nav>
<h3 class="screen-reader-only">Social media</h3>
<ul>
<li><a href="#">Bluesky</a></li>
<li><a href="#">Instagram</a></li>
...
</ul>
</nav>

Without providing accessible names to landmarks, it is impossible to tell the difference between the two navigation menus.
Naming these landmarks with aria-label
gives each a unique name in the rotor menu, “Main navigation” and “Social media navigation,” respectively. Note that you will not need to include the word “navigation” in the name itself because the screen reader announces the landmark’s role. Doing so would cause the screen reader to announce it as “Main navigation navigation.”
We have also changed the heading text to use aria-label because, in this case, we decided the headings might not be useful in the rotor menu’s headings section as part of the document outline. In some cases, the headings might be the better choice.
<nav aria-label="Main">
<ul>
<li><a href="#">Nav link 1</a></li>
<li><a href="#">Nav link 2</a></li>
...
</ul>
</nav>
<nav aria-label="Social media">
<ul>
<li><a href="#">Bluesky</a></li>
<li><a href="#">Instagram</a></li>
...
</ul>
</nav>

Naming the navigation menu with aria-labelledby, the main navigation and social media links are easy to find.
Helpful Tips for Landmarks
Here are some ways you can make important sections of the page easier to find:
- Define and communicate landmark areas on the page.
- If there are multiple instances of a landmark, give them accessible names so users can tell them apart.
- There are multiple ways to name a landmark. Use
aria-labelledby
to reference an existing element on the page. Usearia-label
if there isn’t an existing element or if that text would seem strange for screen readers announcing page content.
Headings
Landmarks are great for helping users find specific sections on the page, but they are not the most useful part of the rotor. Additionally, they don’t help users scan the actual content of the page. That task belongs to headings.
Headings communicate the document structure and enable the rotor to help users navigate to specific sections of the page. Think about how you ingest information on a Word document or Google Pages outline. That is what this part of the rotor menu does. If headings aren’t ordered properly, the document structure will not convey content correctly.
The code below jumps directly from level 1 to level 3. This is a common accessibility defect, and usually happens when heading levels are chosen because of their font size instead of their semantic value. For a sighted user, this detail might go unnoticed because the visual hierarchy remains clear. However, for screen readers, it is difficult to determine the true page structure.
<h1>Improper nesting order</h1>
...
<h3>Section of the page</h3>
...
<h4>Subsection of the page</h4>
...

Using HTML elements set heading font sizes fails to properly communicate the page structure to screen readers by jumping from level 1 to level 3.
Always use semantic headings, and always use the correct order. To change the heading’s font size or appearance, use CSS. Many design systems include utility classes, but use whichever option makes the most sense. Below is the same document using utility classes to set the font size. Now, the rotor menu displays the proper document structure.
<h1>Proper nesting order</h1>
...
<h2 class="font-size-lg">Section of the page</h2>
...
<h3 class="font-size-md">Subsection of the page</h3>
...

In this example, we are using CSS to set the font size of the headings. The visual design has not changed from the previous example but the nesting order is now correct.
Helpful Tips for Headings
Headings play a pivotal role in web accessibility. When used properly, headings not only enhance the structure of your content but also make it more navigable for screen reader users. Here are some key tips to help you get them right:
- Stick to just one
<h1>
tag for each page. This establishes a clear, hierarchical structure that benefits screen readers and search engines. For users relying on assistive technologies, navigating directly to the H1 helps them understand the page’s primary focus instantly. - Reserve the H1 for the page title, not the logo. The H1 should always represent the page’s main title, not a branding element. This ensures your content hierarchy is logical and supports better accessibility for screen reader users.
- Use headings (H2, H3, etc.) to break your content into digestible sections. If you have important information, like customer service details, buried at the bottom of a page, create a distinct heading for it. This helps users with screen readers quickly locate and navigate to relevant sections without sifting through unrelated content.
- Don’t choose heading levels because of their font size. Make sure headings use a logical order and resize with CSS if necessary.
- Use headings to make specific pieces of content easier to find and navigate.
- Help visually impaired users orient themselves in the content by placing a heading that only screen readers can access.
Links
The links menu in the rotor presents a list of every link on the page. However, that list strips any context around where the links are displayed, which is why link names must be able to convey meaning in isolation.
For example, let’s say a company’s About page displays cards for 20 people, and each card has a link that reads “view details” and opens a page with more information about that person. The visual presentation is perfectly sufficient. Each card is nicely contained by a border, and inside that border is a name, maybe a photo, and the “view details” link. Sighted users would likely understand exactly what happens when they click that link.
In this example, the rotor’s links menu will present 20 instances of “view details” in a row, which isn’t particularly useful. Likewise, for someone using tab navigation with a screen reader, they would hear “view details” with each press of the tab key, creating unnecessary work to figure out where those links actually go.
<div class="card">
<header class="header">
<h2>Luna</h2>
</header>
...
<div class="card-footer">
<a href="#" class="button">View details</a>
</div>
</div>

For each of the cards depicted behind the rotor, sighted users will have no difficulty determining what happens when they click the “View details” button. In the rotor menu, it is impossible to know the difference between the links.
To improve this, one option is to include extra text that will only be available to screen readers. This approach conveys what is important to screen readers while retaining a clean and consistent visual presentation.
<div class="card">
<header class="header">
<h2>Luna</h2>
</header>
...
<div class="card-footer">
<a href="#" class="button">View details <span class="screen-reader-only">about Luna</span></a>
</div>
</div>

By adding text available only to screen to readers, we provide clarity to each link without cluttering the visual presentation for sighted users.
Another option that eliminates the need for visually hidden text is to use slightly different wording. Using the example above, updating the link name to “More about Luna” would solve the problem. This has some advantages over using visually hidden text, but could create visual tension depending on the user interface (UI).
Helpful Tips for Links
Here is how you can make the rotor’s list of links more useful:
- Think about how hidden text can add clarity to link text, especially when the link text is repetitive in the UI. Be sure the hidden text is placed at the end of the text that appears in the UI.
- Don’t use the same link text for different URLs.
- Indicate when links will download a file using either “Download” or “(PDF)” in the link text.
- Always link phone numbers.
- In general, write link text that makes sense in isolation, both on the page and inside the rotor menu.
Form Controls
The rotor menu will also list all form controls on the page, such as inputs, select boxes, text areas, or button elements. Each form element in the rotor menu is displayed by its name, most often conveyed by its label or internal text.
Form controls can include attributes such as required, invalid, disabled, or read-only to convey their state. However, if these states are indicated only visually—through styling or without any programmatic associations—assistive technology users cannot perceive them. The rotor menu will provide the field’s value, if there is one, before the label.
Note that in a real-world application, the value of an input would not indicate if a field was disabled or read-only, as the placeholder values in the following examples do.
<!--Standard input-->
<label for="input-type-text">Input 1</label>
<input id="input-type-text" type="text">
<!--Required input-->
<label for="input-type-text2">Input 2 <span aria-label="required">*</span></label>
<input id="input-type-text2" type="text">
<!--Input with error-->
<label class="label--error" for="input-error">Input 3</label>
<span class="error-message" id="input-error-message">Helpful error message</span>
<input class="input--error" id="input-error" type="text" aria-describedby="input-error-message">
<!--Disabled input-->
<label class="label--disabled" for="input-type-text4">Input 4</label>
<input class="input--disabled" id="input-type-text4" value="this is disabled" type="text">
<!--Readonly input-->
<label class="label--readonly" for="input-type-text5">Input 5</label>
<input class="input--readonly" id="input-type-text5" value="this is readonly" type="text">

Using CSS alone to style form fields will not convey important information to the rotor menu.
If you were scanning through the page on the rotor menu, could you tell which fields were required, had errors, or were disabled? The answer is no.
Using HTML attributes, each field’s state is conveyed.
<!--Standard input-->
<label for="input-type-text">Input 1</label>
<input id="input-type-text" type="text">
<!--Required input-->
<label for="input-type-text2">Input 2 <span aria-label="required">*</span></label>
<input id="input-type-text2" type="text">
<!--Input with error-->
<label class="label--error" for="input-error">Input 3</label>
<span class="error-message" id="input-error-message">Helpful error message</span>
<input class="input--error" id="input-error" type="text" aria-describedby="input-error-message">
<!--Disabled input-->
<label class="label--disabled" for="input-type-text4">Input 4</label>
<input class="input--disabled" id="input-type-text4" value="this is disabled" type="text">
<!--Readonly input-->
<label class="label--readonly" for="input-type-text5">Input 5</label>
<input class="input--readonly" id="input-type-text5" value="this is readonly" type="text">

When we use the appropriate HTML attributes, the rotor will convey to use important characteristics about the form fields.
- Standard text fields will be read as “edit text.”
- Read-only text fields will be read as “text” when using the
readonly
attribute. - Required fields will get read as “required” when using the
required
attribute. Here, we can hide the asterisk from screen readers because required will be conveyed by the attribute. - Disabled fields will get read as “dimmed” when using the
disabled
attribute. - Invalid fields will get read as “invalid” when using the
aria-invalid="true"
attribute.
Helpful Tips for Form Controls
A few things to consider when programming form controls for accessibility:
- Make sure all form controls are properly labeled.
- Ensure attribute information is conveyed similarly for sighted and visually impaired users alike.
- Don’t rely solely on ARIA or attributes. Make sure that sighted users can clearly understand these attributes as well.
Tables
Tables also appear in the rotor menu. In addition to semantic markup, they should have an accessible name. Providing accessible names is especially important when there are multiple tables on a page, where a user might want to use the rotor to find a particular one. While failing to name each table may not show in some accessibility checkers, the rotor will outline the table section as follows:
- “3 columns, 5 rows”
- “2 columns, 4 rows”
<h4>Top documents</h4>
<table>
...
</table>
<h4>Store hours</h4>
<table>
...
</table>

Similar to ARIA landmarks, it is impossible to determine the contents of each of the tables without an accessible name.
Using <h4>
+ <table>
will not give the table a proper name in the rotor menu. You must name it with <caption>
, aria-label
, or aria-labelledby
. This will give the table a name in the rotor menu, which will also be announced when the user navigates to the table. With a name, the rotor menu will now list:
- “Top documents 3 columns, 5 rows”
- “Store hours 2 columns, 4 rows”
<table>
<caption>Top documents</caption>
...
</table>
<table>
<caption>Store hours</caption>
...
</table>

Using an accessible name for each table, we can convey important information about their contents to screen readers.
Helpful Tips for Tables
Here are some tips to make specific tables easier find and navigate to using the rotor:
- Every table should have an accessible name
- If the name of the table is included visually, use
<caption>
oraria-labelledby
- If the visual design does not include a table caption, you will still need to name the table with
aria-label
Further Accessibility Reading
For a deep dive on web accessibility, check out Coforma’s Accessibility Playbook.
It was created for anyone who values accessibility and is committed to designing solutions that work for all users. Written by Coforma’s accessibility experts, it’s a cross-functional guide to help project teams build skills, gain knowledge, and strengthen their accessibility practices and delivery capabilities.