Web Accessibility Part 2: Practical Essentials for Frontend Developers
In Part 1 of this series, we looked at why accessibility matters and how the European Accessibility Act is reshaping digital standards across the EU. Now it's time to get practical.
In this second part, we dive into hands-on accessibility best practices for front-end developers. This guide is packed with actionable tips that will help you build interfaces that are inclusive, compliant, and simply better for everyone.
If you haven’t read Part 1 yet, start here: Why It Matters, Who It Helps, and What Changes Under the European Accessibility Act.
Introduction
Adapting a website or web application to EAA is not just about audits and documentation – it is primarily about the daily decisions made by the people who write the code. Below are practical tips that every frontend developer should know and apply when working on accessibility.
1. Semantic HTML
The basis of the accessible front-end is the use of appropriate HTML tags. Elements such as <header>, <nav>, <main>, <section>, <aside>, <button>, or <label> not only organize the code but also enable assistive technologies (e.g., screen readers) to interpret the structure and function of the page correctly.
<!-- ❌ Incorrect -->
<div onclick="submitForm()">Save</div>
<!-- ✅ Correct -->
<button type="submit">Save</button>
Another example would be a website navigation:
<!-- ❌ Difficult to scan an HTML document, poor accessibility-->
<div class="nav">
<div class="nav-links">
<p><a href="#">Home Page</a></p>
<p><a href="#">Articles</a></p>
<p><a href="#">About</a></p>
</div>
</div>
<!-- ✅ With semantic HTML elements -->
<nav>
<ul>
<li><a href="#">Home Page</a></li>
<li><a href="#">Articles</a></li>
<li><a href="#">About</a></li>
</ul>
</nav>
2. Headings
Headings (<h1> to <h6>) play a key role in the structure of an HTML document, not only from an SEO perspective, but above all in terms of accessibility. Headings enable people using screen readers to quickly orient themselves in the page layout and navigate its content. Incorrect use of headings disrupts the document's semantics and makes navigation difficult.
It’s like a book without a table of contents – difficult to search, chaotic, and unreadable. The same applies to websites: assistive technologies use headings to outline the page structure, allowing users to quickly “scan” the content.
Key principles:
- Each page should contain only one h1 heading, which represents the main topic or title of the subpage,
- The order of headings should be logical and hierarchical, e.g., h1 → h2 → h3, without skipping levels without justification. This helps users understand the structure of the content,
- Do not use a lower level heading (h3, h4, etc.) just because it looks better visually. If it is semantically the main title, use h1 and style it appropriately with CSS,
- Avoid using non-semantic elements (e.g., p, span) as substitutes for headings—even if they look similar. Assistive technologies do not recognize them as structural reference points.
<!-- ❌ Incorrect structure – hierarchy omitted -->
<h1>Company blog</h1>
<h3>Product news</h3>
<h4>Version 2.0 – what's new?</h4>
<h2>Guides</h2>
<!-- ✅ Correct structure -->
<h1>Company blog</h1>
<h2>Product news</h2>
<h3>ersion 2.0 – what's new?</h3>
<h2>Guides</h2>
<h3>How to implement accessibility step by step?</h3>
The correct structure of headers is not only about aesthetics and order – it is a real convenience for thousands of users who use the Internet differently than most. It is also one of the pillars of compliance with WCAG standards and the European Accessibility Act.
3. Forms
Each form should contain well-described fields. <label> must be linked to the form field via for/id. Additional messages (e.g., error messages) should be linked to the field via aria-describedby (allows screen readers to identify the message as related to a given field) and placed in a container with aria-live (signals to assistive technologies that there has been a significant change in the content on the page).
A) Linking form fields to labels
Correctly linking a label to an input field improves form usability – both for people with limited mobility (a larger clickable area) and for users of screen readers (the label is read automatically when the field is in focus).
There are two main ways to link:
<!-- ✅ Implicit association – the form field is located as a child of the label -->
<label>
Name:
<input type="text" name="name" />
</label>
<!-- ✅ Explicit association – the label and input are associated via for and id -->
<label for="user-name">Name:</label>
<input type="text" id="user-name" name="name" />
Both methods are correct. The most important thing is that the relationship is clear and can be interpreted by assistive technologies.
B) Error messages related to specific fields
If the form contains errors, they must be directly related to the relevant fields – otherwise, people with disabilities may not understand what is wrong.
A common mistake is to display an error message next to a field without any logical connection. In this case, the screen reader will not convey any information to the user.
<!-- ✅ Properly linked form label and error message using aria-describedby -->
<label for="username">Username:</label>
<input
type="text"
id="username"
name="username"
aria-describedby="username-error"
/>
<p id="username-error" aria-live="polite">
Username is required.
</p>
In real implementations, the element with the message (<p> or <div>) should be in the DOM tree from the beginning, but initially remain empty, so that when content is added, the message is recognized as an update and read automatically.
C) Support for autocomplete
Disabling autocomplete can be frustrating, especially for people with motor impairments who must enter the same data every time. Although the autocomplete attribute is enabled by default, it is worth explicitly specifying it if necessary so that the browser knows what type of data the field expects.
<!-- ✅ Proper use of autocomplete attribute for improved form usability -->
<label for="first-name">First Name:</label>
<input
type="text"
id="first-name"
name="first-name"
autocomplete="given-name"
/>
D) Use of appropriate field types
HTML offers over 20 types of form fields (type=“...”), which are worth using consciously. It allows users, especially mobile users, to receive customized keyboards and suggestions.
Examples:
- type="email" – enables validation and a mobile keyboard with @,
- type="number" – launches a numeric keyboard,
- type="tel" – facilitates entering phone numbers.
E) Clear focus indicator
Each active form field should have a visible focus indicator (:focus). It lets users know which field is active, making navigation and data entry easier. Do not remove the default border without providing a clear alternative.
4. Modals
Modals (dialogs) are commonly used in complex web applications, but implementing them correctly from an accessibility perspective is challenging. Key aspects include focus management, keyboard navigation, and blocking access to content behind the modal.
A) Built-in <dialog>
The easiest and most recommended way to create an accessible modal is to use the semantic <dialog> element, which browsers now support. It automatically supports many accessibility mechanisms, including keyboard navigation and closing the window with the Esc key.
B) Custom modal
When building your modal component (e.g., in React, without using <dialog>), you must take care of accessibility yourself. Here are some key points:
Focus management
When a modal window is opened, users who use a keyboard or screen reader may unknowingly remain on the button that launched the modal, making navigation difficult. It can also lead to the same window being launched multiple times. To prevent this, you should:
- Set the focus on the first interactive element in the modal window immediately after it opens.
- Use a “focus trap,” a mechanism that keeps the focus inside the modal and prevents users from moving to elements on the background page.
- After closing the modal, restore the focus to the element that opened it.
Hiding inactive content
Although the modal visually obscures the rest of the page, users of screen readers can still “browse” its content. To prevent this, it is worth using the inert attribute on the main container of the page (e.g., <main>), which will make the background content inaccessible and non-interactive.
Closing a modal with the Escape key
The ability to close a modal with the Esc key is good UX practice and an absolute convenience for people with motor impairments. Users expect this behavior as the standard way to exit a dialog box.
5. Ensuring adequate text contrast
WCAG 2.1 and level AA require a minimum contrast ratio of 4.5:1 for standard text and 3:1 for large text. Lack of contrast is among the most common barriers for people with impaired vision or color blindness. It is worth using contrast analysis tools (e.g., WebAIM Contrast Checker, Coolors Contrast Checker).
In the image above, the left is the form as seen by a user with normal vision. The right is the same form, but as seen by people with color perception impairments (e.g., color blindness). The lack of clear contrast makes the elements difficult or even impossible to read.
The use of color should not be the only indicator of meaning (e.g., red border or text in a color indicating an error). It is worth supporting this with additional cues: an icon, text label, bold font, symbol, or asterisk. Below is an example of a better form design with an extra icon and a larger font size.
6. Keyboard operation
A well-designed website should be accessible to users who navigate using only the keyboard (e.g., Tab, Enter, Esc). It is worth regularly testing usability without using a mouse and ensuring a logical focus order. The proper HTML structure and thoughtful use of the tabindex attribute are key here.
In most cases, you don't need to set the tab order between elements manually. It's enough to use native, semantic tags (<button>, <a href="">, <input>), which automatically support Tab navigation. Manually overriding the tab order (e.g., tabindex="1", tabindex="2") is challenging to maintain, often inconsistent with the DOM, and considered an antipattern.
If a custom component (e.g., a drop-down menu) requires keyboard support, use:
- tabindex="0" – adds an element to the natural focus order (e.g., a custom button),
- tabindex="-1" – allows you to set the focus programmatically, so you can exclude an element from sequential keyboard navigation (e.g., after opening a modal),
- Never use tabindex > 1 – may disrupt the intuitive navigation order.
The tabindex attribute should only be used for interactive elements. Adding it to static content (e.g., div, p) introduces unnecessary confusion and may make it difficult for users with motor or visual impairments to use.
<!-- ✅ Properly operable custom button -->
<div
role="button"
tabindex="0"
aria-pressed="false"
onclick="toggleState()"
onkeydown="
if(event.key==='Enter'||event.key===' ') {
toggleState();
}"
>
More...
</div>
<!-- ✅ Element with tabindex="-1" for temporary focus -->
<div id="modal" tabindex="-1" role="dialog" aria-modal="true">
<h2>Modal Title</h2>
<p>Description...</p>
</div>
<script>
document.getElementById('modal').focus();
</script>
A well-organized focus order increases the predictability of the interface and makes it easier to use, both for people who choose to use a keyboard and those who have no other option.
7. Visible focus
The visibility of the active element border (:focus) is crucial for people navigating the site without a mouse. Avoid removing it (outline: none) without providing a visual alternative. Many CSS frameworks hide it by default – this should be consciously controlled.
/*✅ Visible and accessible focus indicator for keyboard navigation*/
button:focus {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
8. Appropriate sizes of interactive elements
Buttons, links, and other interactive components should be of an appropriate size. A minimum of 44 × 44 px is recommended, especially for people with motor difficulties or mobile users.
9. Alternative texts for graphics
Each image should include an alt attribute that describes its function. Assistive technologies, such as screen readers, enable users with visual impairments to understand the image's content. If an image is purely decorative, you can use alt="" to tell the screen reader to skip it. Additionally, the alt attribute supports SEO by helping search engines better understand the content of the page's content.
<!-- ✅ Descriptive and meaningful alt text for screen reader users -->
<img src="avatar.jpg" alt="Profile picture of user John Doe" />
10. SVG icons accessibility
SVG icons are among the most overlooked elements in website accessibility. How an icon is implemented is very important for users who use screen readers. The key here is to distinguish between decorative icons and semantic icons.
A) Decorative icons
If an icon does not convey any additional content (e.g., an arrow next to the “Submit” button), it should not be read by a screen reader. In such a case, it should be hidden using the aria-hidden=“true” attribute.
In addition, for compatibility with older browsers (e.g., Internet Explorer), it is worth adding focusable="false" to prevent the icon from accidentally receiving focus.
<!-- ✅ Decorative SVG icons hidden from screen readers using aria-hidden and focusable -->
<button>
<svg
class="paper-airplane"
aria-hidden="true"
focusable="false"
>
...
</svg>
Submit
</button>
<a href="#">
<svg
class="home"
aria-hidden="true"
focusable="false"
>
...
</svg>
Home Page
</a>
B) Semantic icons
Icons that replace text or have a specific functional meaning (e.g., an arrow in pagination navigation), they must be recognizable and understandable to a screen reader. In such cases, it is worth using the <title> and <desc> elements, which provide a short description and broader context of the icon's function.
- title – short alternative text that serves as a label,
- desc – an expansion that provides the user with more information.
The aria-labelledby and aria-describedby attributes link these descriptions to the icon.
<!-- ✅ Fully accessible SVG icon with title and description -->
<svg
class="right-arrow"
role="img"
aria-labelledby="arrow-title"
>
<title id="arrow-title">Pagination arrow</title>
<!-- SVG content -->
</svg>
<svg
class="right-arrow"
role="img"
aria-labelledby="arrow-title"
aria-describedby="arrow-desc"
>
<title id="arrow-title">Pagination arrow</title>
<desc id="arrow-desc">
Arrow to go to the next page
</desc>
<!-- SVG content -->
</svg>
Thanks to this labeling, the screen reader will not only inform the user about the presence of the icon, but also its function – which is crucial for understanding the interface in the case of icon buttons.
11. Motion and animations
Animations can increase the usability of a website by helping users navigate the site, e.g., when switching between states. However, for some users (e.g., those sensitive to light and motion), dynamic elements such as flashing graphics, automatic carousels, or scrolling animations can be annoying or harmful. The prefers-reduced-motion media query checks whether the user has disabled animations on their device and allows CSS styles to be applied according to their preferences:
.wobble-animation {
animation: wobble 0.2s ease-in 3 alternate;
background-color: purple;
}
/* ✅ if the user prefers reduced motion apply these styles */
@media (prefers-reduced-motion: reduce) {
.wobble-animation {
animation: none;
}
}
/* ✅ or in a slightly leaner way - define the animations only when the user has no motion preferences */
@media (prefers-reduced-motion: no-preference) {
.wobble-animation {
animation: wobble 0.2s ease-in 3 alternate;
}
}
12. Accessibility of video and audio content
Videos, podcasts, and audio recordings should also be accessible to people who are deaf, hard of hearing, blind, or have other disabilities. It is not only a sign of empathy — it also benefits SEO: search engines do not “read” videos, but they do index subtitles and transcripts.
A) Subtitles and transcripts
Captions and transcripts are essential. It is best to prepare them by professionals, but you can also create them yourself. Captions are divided into:
- Closed captions – they can be turned on, turned off, translated, and edited,
- Open captions – they are embedded in the image, always visible, but cannot be edited.
Automatically generated subtitles (e.g., on YouTube) can be inaccurate and often omit background sounds (“murmurs,” “laughter,” “screams”). Transcripts should contain all spoken text and a description of crucial visual information, preferably in several languages if the website has a multilingual version.
B) Audio description
For blind people, it is worth adding an audio description – a narrated description of what can be seen in the recording. You can also use so-called “built-in audio description,” which is a consciously chosen way of speaking: for example, instead of “as you can see in the chart,” it is better to say “revenues increased by 59% compared to 43% last year.”
C) Player
The media player must support:
- Subtitle display,
- Keyboard support,
- Full screen,
- Alternative audio tracks and transcripts.
D) Safety
Avoid automatic playback of flashing animations and videos. Such content may be harmful to people with photosensitive epilepsy or migraines. If you must publish such material, add a warning and turn off autoplay.
13. ARIA attributes
It is worth remembering that semantic HTML takes precedence over ARIA attributes. aria-* attributes are helpful in complex components (e.g., custom modals, dropdowns) but should not be overused as replacements for correct markup.
It is worth paying special attention to two ARIA attributes that are crucial in everyday component coding, especially in the context of elements that do not contain visible text or are decorative:
A) aria-label – text description of an element that is not visually visible
This attribute allows you to give an interactive element a description that will be read by a screen reader – even if the button or link does not contain text, but only an icon. For example, a button with a magnifying glass icon can be described as “Search”:
<!-- ✅ Accessible icon-only button with clear aria-label -->
<button aria-label="Search">
<SearchIcon />
</button>
However, it is essential to remember that aria-label should only be used on interactive elements (e.g., button, a, input). Using it on static elements (e.g., div, span) may be ignored by screen readers or lead to illegible and contradictory messages.
B) aria-hidden – hiding an element from the screen reader
This attribute informs assistive technology that a given element should be skipped when reading, even though it remains visually visible. This solution works perfectly for decorative elements, icons, or content without semantic value.
<!-- ✅ Decorative logo hidden from assistive technologies using aria-hidden -->
<div>
Company <CompanyLogo aria-hidden="true" />
</div>
Thanks to aria-hidden, the icon will not be read by a screen reader, which reduces semantic noise and improves the user experience for visually impaired users.
Accessible To-Do List
Finally, I encourage you to check out a simple to-do list application I have prepared as an example of how some of the accessibility solutions described above can be implemented in practice: Web Accessibility Todo List.
The live version of the application is available at: https://web-accessibility-todo-list.netlify.app/
Conclusion
Creating accessible interfaces is not just about ticking boxes; it's about empathy, clarity, and craftsmanship.
Every aria-label, properly structured heading, and readable contrast level contributes to a better experience, not only for users with disabilities but for everyone.
Accessibility is not a feature; it's the mark of a well-built product.
Keep iterating, testing, and learning. The more inclusive the web becomes, the better it will work for everyone.