Table of content
- 1 - Learn how to use a screen reader
- 2 - Gain good knowledge of ARIA
- 3 - Use semantic HTML
- Basic concepts
- Accessibility in base components
- Other improvements
- Last words
This is the third part of the series (after why accessibility matters and designing accessibly). It covers essential principles (not all of them, that’s almost impossible), practical advice, and examples for building accessible websites.
I’ll share the guidelines our team followed to make our site accessible and reach a double-A conformance level.
We’ll cover writing clear image descriptions, navigating with just the keyboard, and a lot more, so everyone can browse your site like a pro. :)

1 - Learn how to use a screen reader
Before getting into the code, you need to understand how to use a screen reader. It’s a fundamental skill that helps you grasp the challenges users face when navigating your site.
Most of our team uses VoiceOver since we’re on macOS. On Windows there are alternatives like Speechify, JAWS, and NVDA.
Taking the time to get familiar with one will give you valuable insight into the user experience.

Most commonly used keys
- Tab and Shift+Tab: move forward and backward through interactive elements on a page, such as links, form fields, and buttons.
- Arrow keys: navigate within a block of text or move between components like headings, paragraphs, and lists. Users can go up, down, left, or right to explore content in detail.
- Nodes: navigating by nodes lets you explore and interact with every element, like moving from one piece of the puzzle to the next. In VoiceOver you do this with
Control + Option + Arrow key.
- Enter or Spacebar: activate buttons, links, or form controls. When the user lands on an interactive element, they press Enter or Spacebar to trigger the action or follow the link.
- Headings: many screen readers offer shortcuts to jump between headings. For example, pressing the “H” key moves to the next heading, which makes it easier to skim a page’s structure.
- Search: screen readers usually include a search function so users can find specific words or phrases on a page without navigating through everything. There’s also a very useful shortcut in VoiceOver to list all headings, links, and form controls:
Control + Shift + U.

2 - Gain good knowledge of ARIA
For this second tip, it’s worth giving some context about what ARIA actually means. ARIA stands for Accessible Rich Internet Applications. It’s a set of guidelines and tools designed to make websites more usable for people with disabilities.
ARIA adds extra information to elements like buttons or links so screen readers and other assistive technologies can understand them better. It’s a way to give a hand to people who can’t see the screen or use a mouse, so they can navigate and interact with sites smoothly.
Most common ARIA attributes
These are the ARIA attributes we mostly used while taking the site to double-A conformance. There are more out there, but you should at least be familiar with these:
- aria-hidden: setting
aria-hidden="true"tells assistive technologies to skip the element in the accessibility tree. Use it for non-interactive elements.
<button class="icon-button">
<span class="visually-hidden" aria-hidden="true">Close</span>
<i class="fas fa-times"></i>
</button>
- aria-label: provides a text alternative for an element when no visible label is present.
<button class="icon-button" aria-label="Search">
<i class="fas fa-search"></i>
</button>
- aria-labelledby: associates an element with another element that serves as its label.
<div id="alert-message" role="alert" aria-labelledby="alert-heading alert-description">
<h3 id="alert-heading">Important Info!</h3>
<p id="alert-description">Please be aware of the upcoming changes.</p>
</div>
The attribute value "alert-heading alert-description" matches the IDs of the heading (<h3>) and paragraph (<p>) that label and describe the alert.
With aria-labelledby, you link the <div> to the heading and description. The screen reader reads the full text inside those elements.
- aria-describedby: associates an element with another element that describes its purpose.
<label for="username">Username:</label>
<input type="text" id="username" aria-describedby="username-description">
<p id="username-description">Please enter a unique username consisting of letters and numbers.</p>
A common case is providing extra information for inputs like usernames, emails, and passwords, where the user has to follow a specific format.
- aria-expanded: indicates whether a collapsible element is currently expanded or collapsed.
<button id="toggle-button" aria-expanded="false" onclick="toggleContent()">
Toggle Content
</button>
By updating aria-expanded based on the state of the content, the screen reader can announce the current state to the user.
- aria-disabled: indicates that an element is disabled and cannot be interacted with.
<button aria-disabled="true">Submit</button>
- aria-live: indicates that content inside an element should be announced to the screen reader as it changes. We often use this to notify the user of form errors.
<div aria-live="polite" id="status-message">
New message received: You have 1 unread notification.
</div>

- role: defines the purpose or type of an element on the page.
The role attribute can be applied to many HTML elements, like <div>, <span>, <button>, <nav>, and more. Common values include “button”, “link”, “navigation”, “heading”, “list”, “form”, and “banner”. Each one conveys a specific meaning and behavior to assistive technologies.
<div role="alert" aria-live="assertive">
<p>This is an important alert message!</p>
</div>
If you need to build a component with a specific role, check the W3C’s ARIA patterns reference.
3 - Use semantic HTML
Semantic HTML is just using tags that give meaning and structure to your content. Instead of slapping any old tag on things, use tags like <header>, <nav>, <article>, and <footer> to convey the structure and purpose of each part of the page.
For example, instead of a <div> for your site’s navigation menu, use the <nav> tag. That tells humans, search engines, and screen readers that the content inside it is the site’s navigation.
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
Examples of semantic usage of HTML tags
Here are key cases where you should reach for the right tag.
<header>: represents the introductory or top section of a page.
<header>
<h1>Welcome to My Website</h1>
<nav>
<!-- Navigation menu -->
</nav>
</header>
<main>: represents the main content of a document.
<main>
<h2>About Me</h2>
<p>I'm passionate about web development, ...</p>
</main>
<article>: represents a self-contained composition, such as a blog post, news article, or forum post.
<article>
<h3>Post title</h3>
<p>Recently, I went on an amazing trip...</p>
</article>
<section>: represents a standalone section within a document.
<section>
<h2>Services</h2>
<ul>
<li>Web Design</li>
<li>Front-end Development</li>
</ul>
</section>
<footer>: represents the bottom section of a document or section.
<footer>
<p>© 2023 MyWebsite. All rights reserved.</p>
<nav>
<!-- Footer navigation goes here -->
</nav>
</footer>
<fieldset>: groups related form fields together, like contact information.
<form>
<fieldset>
<legend>Contact Information</legend>
<label for="name">Name:</label>
<input type="text" id="name" name="name">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<label for="message">Message:</label>
<textarea id="message" name="message"></textarea>
</fieldset>
<button type="submit">Submit</button>
</form>
The main advantage of semantic HTML
Semantic HTML elements give you a logical, well-organized tab order. Keyboard-only users rely heavily on the Tab key to move through interactive elements on a page.
By using the right semantic elements, like <button>, <a>, and properly labeled form fields, you ensure keyboard users can navigate and interact with your site efficiently.
Without Semantic HTML
<section>
<h2>Welcome!</h2>
<p>Some paragraph</p>
<div class="button-container">
<div class="button">Register</div>
</div>
<p>Or, if you have any questions, feel free to <span class="link"><a href="/contact">contact us</a></span>.</p>
</section>
With Semantic HTML
<section>
<h2>Welcome!</h2>
<p>Some paragraph</p>
<button>Register</button>
<p>Or, if you have any questions, feel free to <a href="/contact">contact us</a>.</p>
</section>
This is crucial for people with motor disabilities who can’t use a mouse.
So go ahead, embrace semantic HTML and enjoy the ride!

Basic concepts
4 - Use tabindex, but with caution
tabindex is an attribute you can add to HTML elements to control their keyboard navigation order. It decides which elements receive focus as users move through the page with the Tab key.
By setting tabindex values you can customize the order and make your site more accessible, especially for users who rely on the keyboard.

Best practices for tabindex
Only change tabindex values when you have to. Most interactive elements receive focus automatically in the default Tab order, so only step in when it improves the user experience.
- Possible values: the two recommended values are “0” and “-1”.
If you use tabindex="0" to make an element focusable, the keyboard interaction has to be correct and intuitive. For example, anything presented as a button must respond to both the Enter and Spacebar keys.
A tabindex of 0 ensures these elements can receive keyboard focus and be reached with the Tab key. It opens the door for keyboard users to interact with elements that aren’t normally clickable.
tabindex="-1" takes the element out of the regular navigation flow.
That said, don’t assign “-1” to elements that need to be reachable by keyboard, such as links or buttons that sighted users can easily click with a mouse.
In general, avoid tabindex="-1" unless you have a specific reason for it.
Keep a logical flow: arrange
tabindexvalues to match the visual and reading flow of your content. Users should be able to navigate naturally, without unexpected jumps or skipped elements.Skip non-interactive elements: don’t assign
tabindexto non-interactive elements like paragraphs or images. It keeps the focus on the elements that actually need action or input.
5 - Create proper outlines
Outlines are the visual ring around interactive elements like buttons, links, and form fields when you interact with them.
.focus-outline {
outline: 2px solid blue;
outline-offset: 2px;
}
They give a clear visual cue, usually a border or highlight, showing which element is currently focused. Whether you’re using a keyboard or a mouse, outlines make it easy to identify and interact with the right element on the page.

Best practices for outlines
Visibility: outlines must be visible and clearly distinguishable from the surrounding content. Pick colors and styles with enough contrast, and avoid designs that confuse users or distract from the main content.
Don’t remove outlines: never remove or disable outlines unless you’ve replaced them with an accessible alternative. Removing them disorients users, especially those who rely on keyboard navigation. Some developers still strip outlines out, but don’t do it.
Design consistency: keep a consistent outline style across the site. It helps users get familiar with the visual cues and makes browsing smoother.
If you’re a designer, check the second part of this series.
6 - Allow keyboard navigation throughout your website
Keyboard navigation is one of the most important parts of web accessibility.

First, it’s closely tied to semantic HTML and native elements. When you combine the two, something great happens: users can navigate most of the site with just the keyboard, without any special tweaks or tricks.
So one of the principles we follow when building new components and features is to always use native elements when possible.
Take the dialog element as an example. Instead of building your own modal, use the native component. It already handles escape, focus trapping, the close button, and so on.
<dialog open>
<p>This is a simple dialog!</p>
<form method="dialog">
<button>nice! ok!</button>
</form>
</dialog>
One last tip: take the #nomouse challenge. Try navigating your page and using all of its features with just the Tab key, without touching the mouse. This simple test is usually a good indicator of accessibility. For more info, see nomouse.org.
7 - Identify the language
You need to provide context about the language of the page, plus any paragraphs or components that are in a different language.
The lang attribute
Setting the lang attribute on the HTML tag declares the language of the page. This helps screen readers and other assistive technologies identify and present the content correctly.
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
</body>
</html>
Keep in mind that if a paragraph is in a different language, you have to specify its language like this:
<body>
<!-- English (US) content -->
<p lang="en-US">Explore and enjoy our diverse content.</p>
<!-- Hindi content -->
<p lang="hi">हमारी विविधतापूर्ण सामग्री का अन्वेषण और आनंद लें।</p>
</body>
There are a few exceptions, like quotations, proper nouns, technical terms, foreign words like “Sushi”, or the language selector itself:

8 - Ensure ARIA attributes are in the correct language
If your page is primarily in English, make sure your ARIA attributes are in English too.
If the user switches to another language, the ARIA attributes should switch with it. Avoid mixing languages as much as possible to keep the experience consistent and clear.
9 - Texts must be resizable
To make your site more user-friendly and accessible, the text has to be resizable.
Ideally, users should be able to increase the font size by up to 200% without any content getting cut off or losing functionality.
In general, use em units for font sizes.
div.a {
font-size: 14px; // :(
}
div.b {
font-size: 1rem; // :)
}
div.c {
font-size: 1.2em; // :)
}
Using scalable units like em and rem lets users control the scale of the site, which is what gets us the accessibility we’re after.
If you’re considering rem, keep in mind that getting every text element to scale up and down accurately can be tricky.
Either way, avoid px for font sizes. They simply don’t scale.
10 - Use headings in the proper order
When structuring your content, use headings in a logical hierarchy.
Start with the main heading (usually <h1>) for the page title or main section, then <h2> for major subsections, and so on.
This hierarchy helps users understand how the content is organized and lets screen readers navigate the page more effectively.
<h1>Best practices for Web Accessibility!</h1>
<h2>Basic concepts</h2>
<h3>Headings</h3>
<h4>Best practices for headings</h4>
....
<h2>Advanced concepts</h2>
<h3>Creating an accessible component library</h3>
Stick to a consistent heading order without skipping levels. For example, don’t jump straight from <h2> to <h4>. Skipping levels confuses screen readers and makes it harder for users to follow the structure.
One final tip for this section: place important interactive elements higher up on the page.

Accessibility in base components
11 - Make images accessible
Let’s go through some easy-to-implement techniques to make your images more accessible.
- Provide descriptive alt text: use concise, descriptive language to convey the content and purpose of the image.
<img src=".jpg" alt="A group of friends enjoying a picnic in the park">
- Decorative alt: sometimes you’ll include decorative images that don’t add meaningful information. For those, use empty alt text or the
aria-hiddenattribute so screen readers skip them.
<img src="dec.jpg" alt="" aria-hidden="true">
- Provide captions and image descriptions: for complex images, charts, or infographics, add extra context with captions or detailed descriptions.
<figure>
<img src="info.jpg" alt="Data visualization of population growth">
<figcaption>Data visualization of population growth in the last decade</figcaption>
</figure>
File formats: use widely supported formats like JPEG, PNG, or GIF for regular images. For complex graphics or images with transparency, consider SVG. These formats ensure compatibility with most browsers and assistive technologies.
Implement responsive images: use the
srcsetattribute to provide multiple image sources of varying sizes.
<img srcset="small.jpg 320w,
medium.jpg 768w,
large.jpg 1200w"
sizes="(max-width: 768px) 100vw,
50vw"
src=".jpg" alt="A responsive image">

12 - Create icons for everyone
Instead of using icons as purely decorative elements, consider wrapping them in semantic HTML like <span> or <i> and providing a proper text alternative.
<span class="icon" aria-hidden="true">📞</span>
<span class="sr-only">Phone</span>
Icon fonts and SVGs are highly recommended for accessibility. They scale without losing quality and work well across devices and browsers.
For complex SVGs, use title and desc elements along with aria-labelledby, like this:
<svg
role="img"
aria-labelledby="svg-title svg-description"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 48 48"
xml:space="preserve">
<title id="svg-title">A short title that summarizes the purpose of the svg!</title>
<desc id="svg-description">A description with more details.</desc>
<path d="..."/>
</svg>
13 - Don’t forget states in buttons
Buttons are fundamental web design elements. They provide interactivity and act as key call-to-action components.
Beyond using the semantic <button> tag with descriptive text like “Send Message”, you should also provide visual feedback through :focus and :hover states.
<button class="primary-button">Save Changes</button>
.primary-button:hover {
background-color: #42b983;
color: white;
}
.primary-button:focus {
background-color: #342455;
color: black;
}
There’s also the disabled state, which requires aria-disabled if the button is still focusable:
<button aria-disabled="true">
Continue
</button>
If the button is fully disabled, then:
<button disabled>
Continue
</button>
For toggle buttons, use aria-pressed="true/false". For menus, use aria-expanded="true/false".

14 - Create descriptive links
Sometimes it’s hard to decide between a button and a link. Our rule is simple: if the user is taken to another page or location, it should be a link.
Even if a link looks like a big shiny button, it’s crucial to use the <a> element when coding it.
This is a design tip we covered in the second part of the series too: avoid generic link labels like “click here” or “read more”.
Note: links shouldn’t be the raw URL unless it’s meant for printing (like in a Word document). And if a link opens in a new window, make sure to tell the user.
Example: Article about Web Accessibility (opens in new tab)

15 - Ensure forms are accessible
This is one of the trickiest parts of web accessibility.
Forms can include a wide range of components: comboboxes, inputs of various types (numbers, dates, etc.), text areas, and many more.
Making each form component accessible is crucial, but covering every one in detail would be insane. So here are some more general, but equally important, tips:
- Semantic HTML (yes, again): when structuring forms, use semantic HTML elements like
<form>,<fieldset>, and<legend>to give them a clear, organized structure.
<form action="#">
<fieldset>
<legend>Do you agree?</legend>
<input type="checkbox" id="chbx" name="agree" value="Yes!" />
<label for="chbx">I agree</label>
</fieldset>
</form>
This lets the screen reader provide context about the field the user has to fill in.
- Use labels: make sure every form field has a descriptive label. Use the
<label>element and tie it to the field with theforattribute, or nest the input inside the label.
<label for="name">Name:</label>
<input type="text" id="name" name="name">
- Clear instructions: include clear, concise instructions to guide users through the form. Place them near the related fields, using extra text or elements like
<p>or<span>.
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<p class="instructions">Please enter a valid email address.</p>
- Provide error validation and feedback: tie error messages to their fields with ARIA attributes, and use visually distinct styles or alert boxes to notify users of errors, while keeping enough color contrast for readability.
Here’s an example, but since this is a complex topic, I recommend reading Sandrina’s post on Accessible Form Validation. Simply amazing.
<input
id="address"
type="text"
required="required"
aria-invalid="true"
aria-describedby="addressError addressHint"
/>
<span>
<p id="addressError">Address cannot be empty.</p>
<p id="addressHint">Remember to include the door and apartment.</p>
</span>
16 - Include captions for videos
This isn’t as common as forms, but it’s generally easier to get right.
First, provide captions:
<video>
<source src="video.mp4" type="video/mp4">
<track src="captions.vtt" kind="captions" label="English" srclang="en" default>
</video>
Second, always make controls available for keyboard users:
<video controls tabindex="0">
<source src="video.mp4" type="video/mp4">
</video>
A few more tips:
Avoid flashing or blinking content that can trigger seizures.
Avoid auto-playing media or animations that can distract users.
Color plays a big role in videos, from subtitles to graphical elements. Make sure there’s enough contrast between text and background.
Pick an accessible video player.
17 - Provide transcripts or audio descriptions for audio content
In this case, you can provide a transcript like this:
<audio controls>
<source src="audio.mp3" type="audio/mpeg">
</audio>
<p><a href="audio-transcript.txt">Read the audio transcript</a></p>
Check that the audio content, transcripts, captions, and audio descriptions are interpreted correctly.
Note: audio descriptions should give a clear, concise narration of the visuals and actions in the video.

18 - Use titles and make iframes responsive
Iframes are versatile tools for embedding external content into your site. But there are a few things you need to do to make them accessible.
Use descriptive titles
When you use an iframe, give it a descriptive title that conveys the purpose and content of the embedded page.
<iframe src="external-page.html" title="Weather Forecast"></iframe>
Make them responsive
Make sure iframes are responsive and adapt well to different screen sizes and resolutions.
<div class="wrapper">
<iframe src='some url' frameborder="0" allowfullscreen></iframe>
</div>
.wrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
padding-top: 25px;
height: 0;
}
.wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Provide a fallback option
In some cases, iframes aren’t supported or accessible for certain users or devices. To make sure everyone can still access the essential information, provide a fallback.
<iframe src="external-page.html">
<img src="fallbac.jpg" alt="Fallback Image: Content Not Available">
<p>If you are unable to view the content, please try accessing it directly using the <a href="external-page.html">fallback link</a>.</p>
</iframe>
Other improvements
19 - Make downloads descriptive
Quick note on this one, because we sometimes run into tricky situations that confuse users.
The tip is simple: use descriptive link text, and include the file extension and file size (when possible).
<a href="document.pdf">Download the User Manual (PDF, 2.5MB)</a>
20 - Create a link to bypass blocks
Bypass blocks are essential for web accessibility. They let users skip repetitive or non-essential content and jump straight to the main content of a page.
An example:
<a href="#main-content" class="skip-link">Skip to Main Content</a>
.skip-link {
position: fixed;
top: 10px;
left: 10px;
z-index: 9999;
padding: 10px;
background-color: #42b983;
color: white;
text-decoration: none;
}
.skip-link:not(:focus):not(:active) {
position: absolute;
top: -9999px;
left: -9999px;
}
.skip-link:focus {
top: 5px;
left: 5px;
background-color: #0066cc;
}
The skip link is positioned fixed and visible by default. But the :not(:focus):not(:active) selector hides it (position: absolute; top: -9999px; left: -9999px;) for mouse users.
It only becomes visible when it receives focus or is active, using the styles in the :focus selector.
This way, the skip link is hidden for mouse users but stays accessible and functional for keyboard users.
You can try it out on dev.to :)

Last words
So there you have it. We’ve covered what I think are the top 20 must-know tips for web accessibility. Special thanks to @pablo_medina and @juanpabloamador for their feedback!
The next article in the series will be about “Common accessibility challenges our team has faced”. Stay tuned.
And remember, making your site accessible isn’t just good practice, it’s the right thing to do.

This post is long, but I hope you’ve picked up something new along the way. If you think it might help others, please hit the like button so more people can find it. ❤️
If you have any thoughts or questions, feel free to leave a comment!

Join the conversation
No account needed — leave your name and a thought. Comments are reviewed before going live.