Web Accessibility with HTML and React: Developer Guide

- Begin with semantic HTML. Native elements provide meaning and, for interactive controls, expected browser behaviour.
- Use real buttons for actions and links for navigation.
- Associate every form control with a persistent, programmatic label.
- Treat placeholder text as an example or hint, not as the field’s only label.
- Use ARIA to communicate information that HTML cannot express—not to replace suitable HTML.
- Remember that ARIA changes accessibility semantics but does not automatically add keyboard behaviour.
- Ensure every interactive feature works with a keyboard and has a visible focus indicator.
- Manage focus when dialogs open, routes change, elements disappear, or errors require attention.
- Announce important dynamic updates without overwhelming screen-reader users.
- Combine automated tools with keyboard testing, screen-reader testing, zoom, and real-user evaluation.
- The Web Content Accessibility Guidelines 2.2 provide the current W3C accessibility standard. They organise accessibility under four principles: content must be perceivable, operable, understandable, and robust.
A website can load quickly, pass functional tests, and look polished while remaining difficult or impossible for some people to use. A checkout flow may depend entirely on a mouse. A form may display errors visually without announcing them to a screen reader. A modal may open without moving keyboard focus, leaving the user interacting with content hidden behind it.
These are not cosmetic defects. They prevent users from completing real tasks.
Web accessibility means designing and developing websites and applications so people with different visual, auditory, motor, speech, and cognitive abilities can perceive, understand, navigate, and interact with them.
For developers, much of this work begins with three decisions:
- Use native HTML elements for their intended purpose.
- Add ARIA only when native HTML cannot express the required semantics.
- Test the resulting behaviour with keyboards and assistive technologies.
This guide explains how to apply those principles in HTML and React, where common accessibility mistakes appear, and how to include accessibility throughout development rather than postponing it until an audit.
What Is Semantic HTML?
Semantic HTML means selecting elements according to the role of the content rather than its visual appearance.
A <nav> identifies a navigation region. A <button> represents an action. A <label> names a form control. Headings communicate the hierarchy of the page. These elements help browsers and assistive technologies construct a meaningful representation of the interface.
Semantic HTML benefits more than screen-reader users. It can improve keyboard interaction, browser behaviour, maintainability, automated testing, reader modes, and search-engine understanding.
However, semantic markup alone does not make an entire interface accessible. Correct HTML must be combined with readable content, sufficient contrast, logical focus order, clear form feedback, alternative text, responsive layouts, and accessible interaction design.
Common Semantic Elements and When to Use Them
| Element | Use it for | Typical example |
<header> | Introductory content for a page or section | Site header or article introduction |
<nav> | A major collection of navigation links | Primary menu or section navigation |
<main> | The page’s primary content | Article, dashboard, or product view |
<section> | A related thematic group, normally with a heading | Features or services section |
<article> | Self-contained, independently meaningful content | Blog post, comment, or news item |
<aside> | Complementary rather than primary content | Sidebar, related links, or note |
<footer> | Closing information for a page or section | Copyright, author, or contact details |
<h1>–<h6> | Hierarchical headings | Page and subsection headings |
<ul>, <ol>, <li> | Actual lists of related items | Steps, features, or grouped links |
<form> | A collection of controls for submitting information | Registration or search |
<label> | A visible name for a form control | Email address or destination |
<button> | An action on the current interface | Submit, save, open, or close |
<a> | Navigation to another location or resource | Internal route or external page |
An element should not be selected merely because its default appearance is convenient. CSS can change presentation; HTML should preserve meaning.
Use Semantic HTML First
Begin with the native element that most closely matches the intended behaviour. Use a button for an action, a link for navigation, and a heading for a heading.
This reduces the amount of accessibility behaviour developers must recreate manually.
Native interactive elements can provide:
- Keyboard focus
- Expected keyboard activation
- Roles exposed to assistive technology
- Form behaviour
- Disabled states
- Browser and operating-system conventions
- More dependable compatibility across devices
Structural semantic elements do not all introduce keyboard behaviour, but they give the page meaningful regions and relationships.
Consider this page structure:
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
<main>
<section>
<h2>Features</h2>
<p>Explore powerful tools.</p>
</section>
</main>
<footer>
<p>© 2025</p>
</footer>The markup communicates that the page contains navigation, primary content, a features section, and a footer. A screen-reader user can navigate through these regions and headings rather than hearing an undifferentiated stream of content.
The heading structure must still be logical. Developers should not select heading levels according to font size or skip levels merely to obtain a particular visual style.
Buttons and Links Are Not Interchangeable
A common accessibility problem is using links and buttons according to appearance instead of behaviour.
Use a link when activating the control moves the user to another URL, route, page section, or downloadable resource. Use a button when it performs an action such as submitting a form, opening a dialog, expanding content, or changing the current interface.
This distinction helps keyboard and screen-reader users predict what activation will do. It also produces appropriate browser behaviour. Links can be copied, opened in another tab, and expose destinations. Buttons participate naturally in forms and support expected activation keys.
Styling a link to look like a button does not change its underlying purpose. The semantic element should follow the action.
Use ARIA When HTML Alone Is Not Enough
ARIA stands for Accessible Rich Internet Applications. It provides roles, states, and properties that can communicate information not represented by ordinary HTML.
ARIA becomes relevant in custom components such as tabs, dialogs, comboboxes, tree views, and live status regions. It can communicate that an accordion is expanded, identify the element controlled by a button, or announce an important asynchronous update.
Common attributes and roles include:
| ARIA attribute or role | Purpose |
aria-label | Provides an accessible name when no suitable visible label exists |
aria-labelledby | Derives an accessible name from visible content elsewhere |
aria-describedby | Associates additional instructions, hints, or errors |
aria-hidden="true" | Removes non-essential content from the accessibility tree |
aria-live | Announces dynamic updates |
aria-expanded | Communicates whether collapsible content is open |
aria-controls | Identifies content controlled by another element |
role="dialog" | Identifies a custom dialog |
role="tablist" and role="tab" | Describe a custom tab interface |
For example:
<button aria-expanded="false" aria-controls="dropdown1">Menu</button>
<ul id="dropdown1" hidden>
<li>Item 1</li>
</ul>The button already has native button semantics. aria-expanded adds the component’s current state, while aria-controls identifies the controlled element.
The value of aria-expanded must remain synchronised with the visible state. ARIA that communicates stale or incorrect information can be worse than omitting that information entirely.
When Should Developers Avoid ARIA?
The W3C’s ARIA Authoring Practices Guide states that “No ARIA is better than bad ARIA.” Incorrect roles and attributes can cause assistive technology to communicate an experience that does not match the visible interface.
Avoid adding ARIA when:
- A suitable native HTML element already exists.
- The attribute repeats information already supplied by HTML.
- The component does not implement the keyboard interaction expected for its role.
- The state cannot be kept synchronised with the interface.
- The implementation has not been tested with assistive technology.
- ARIA is being used to compensate for unclear visual or content design.
Consider this example:
<!-- Avoid this -->
<div role="button">Click me</div>The role tells assistive technology to treat the element as a button, but the role alone does not make the element focusable or add keyboard activation. W3C describes an ARIA role as a promise: assigning role="button" means the developer must also implement the behaviour users expect from a button.
The native version is more dependable:
<button>Click me</button>The browser supplies button semantics, focusability, keyboard activation, and expected interaction without requiring custom recreation.
Accessible Names and Descriptions
Every interactive control needs a meaningful accessible name. The name answers, “What is this control?” An accessible description provides supporting information such as a hint, requirement, status, or error.
Visible text often supplies the best accessible name because sighted users and screen-reader users receive consistent information.
Use aria-labelledby to reference visible labels
aria-labelledby allows an element to receive its accessible name from one or more elements elsewhere on the page.
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Subscribe</h2>
<p>Sign up for weekly updates.</p>
</div>The dialog receives the name “Subscribe” from the heading. This makes the visible heading and the programmatic name consistent.
Let’s Build an Accessible Web Together!
We design and develop web experiences that everyone can use, simple, inclusive, and ADA-compliant.
When aria-labelledby references multiple IDs, assistive technologies combine the referenced text in the specified order. It also has high precedence in accessible-name calculation, so developers should use it deliberately. W3C’s accessible-name guidance explains how names and descriptions are calculated.
Use aria-describedby for supporting context
aria-describedby associates a control with supplementary information. It is useful for format requirements, hints, constraints, and validation feedback.
<label for="email">Email</label>
<input id="email" aria-describedby="email-hint email-error" />
<p id="email-hint">We'll never share your email.</p>
<p id="email-error" style="color:red;">Email is required.</p>In this example, the input’s accessible name comes from the label, while the hint and error provide a description.
In a production form, validation messages should normally appear only when relevant. The interface may also need to communicate that the field is invalid, move attention to a useful error summary, or announce a newly displayed error. aria-describedby creates the relationship, but error timing and focus behaviour still require deliberate implementation.
Comparing Inaccessible and Accessible HTML
Small markup decisions can substantially change how a control behaves.
Inaccessible button
<div onclick="submitForm()">Submit</div>This element responds to a pointer click, but it does not expose button semantics, receive keyboard focus naturally, or support standard button activation.
Accessible button
<button onclick="submitForm()">Submit</button>The native button communicates its purpose and includes standard keyboard behaviour. It also works more predictably with forms and assistive technology.
Inaccessible form input
<input type="text" placeholder="Your Name">Placeholder text is not a reliable label. It disappears after the user types, may have insufficient contrast, and can make it difficult to remember what the field requires.
Accessible form input
<label for="name">Name</label>
<input type="text" id="name">The label remains visible and is programmatically associated with the input. Selecting the label can also move focus to the field.
Accessibility in React Applications
React renders HTML, so the core accessibility rules do not change. Semantic elements, labels, keyboard behaviour, focus order, and ARIA remain the foundation.
The additional challenge is that React applications frequently update the interface without a full page load. Components mount and unmount, dialogs appear, validation messages change, and client-side routing replaces page content. Visual users can often see these changes immediately, while assistive-technology users may need focus movement or an announcement.
Associate labels using htmlFor
React uses htmlFor in JSX to associate a label with a form control:
<label htmlFor="email">Email</label>
<input id="email" type="email" />The value of htmlFor must match the input’s id. Reusable components should generate or accept stable IDs so multiple instances do not produce duplicate values.
React otherwise uses standard aria-* attribute names. The official React documentation confirms that ARIA attributes retain the same naming format as HTML.
Prefer native controls over custom interactive containers
The following component adds a role, keyboard focus, keyboard handling, and click handling to a div:
<div role="button" tabIndex={0} onKeyDown={handleKeyDown} onClick={handleClick}>
Toggle
</div>This illustrates the work required when a non-interactive element is turned into an interactive control. The keyboard handler must reproduce the expected activation behaviour and prevent inconsistent pointer and keyboard outcomes.
Where the control genuinely behaves like a button, a native button remains the stronger starting point. Custom roles are justified only when native HTML cannot express the intended interaction.
Announce important dynamic updates
React applications often change text without moving focus. A screen-reader user may not know that a save completed, a search returned no results, or an asynchronous request failed.
An ARIA live region can announce updates:
<div aria-live="polite">{message}</div>A polite live region waits for the screen reader’s current announcement to finish. It is suitable for useful but non-urgent status updates.
Live regions should be used selectively. Repeated announcements for loading percentages, keystrokes, or minor visual changes can overwhelm the user. Urgent messages may require different treatment, but urgency should not be assigned merely to make an announcement occur sooner.
Keyboard Accessibility
A keyboard-accessible interface allows users to reach and operate all interactive functionality without a mouse.
Developers should verify that:
- Interactive elements appear in a logical focus order.
- Focus is always visible.
- Buttons activate through expected keyboard input.
- Links activate and expose destinations correctly.
- Menus, tabs, dialogs, and other custom widgets follow established keyboard patterns.
- Keyboard focus does not become trapped unintentionally.
- Users can dismiss overlays without using a pointer.
- Sticky headers and overlays do not obscure the focused element.
- Drag-based interactions have an alternative.
Avoid adding positive tabIndex values to force a custom focus order. The DOM order should normally create the correct reading and keyboard sequence. CSS that substantially rearranges visible content can create a mismatch between what users see and the order assistive technology encounters.
WCAG 2.2 adds requirements relating to unobscured focus, target size, and alternatives to dragging, making these behaviours especially important in current interfaces.
Focus Management in React
Focus management becomes necessary when an interface changes in a way that would otherwise disorient a keyboard or screen-reader user.
1. Dialogs
When a dialog opens, focus should move into it. Keyboard focus should remain within the active modal dialog, and closing it should normally return focus to the control that opened it.
Adding role="dialog" and a label communicates the dialog’s semantics, but ARIA does not implement focus movement, dismissal, background inertness, or focus restoration.
2. Client-side routing
A single-page React application can replace most of the page without triggering the browser behaviour associated with a traditional navigation. After a route change, focus may remain on the link or button from the previous page.
The application should provide an intentional destination for focus or otherwise communicate that navigation completed. The correct approach depends on the layout and router, but the user should not have to discover the new page from an unexplained position.
3. Removed and disabled content
If the focused component is removed, focus may fall back to the document body. After deleting an item, closing a menu, or completing a step, move focus to a nearby logical location when necessary.
Do not move focus for every visual update. Unexpected focus movement can be as disruptive as missing focus management.
Accessible Forms and Validation
Accessible forms require more than labels.
Each field should have:
- A persistent label
- Clear instructions
- Programmatic grouping where related controls belong together
- A communicated required state
- Error identification in text
- A connection between the field and its error
- A visible focus state
- Sufficiently large interaction targets
- Validation that does not rely on colour alone
When submission fails, users need to know that errors exist, which fields are affected, and how to correct them. For long forms, an error summary can provide an overview and links to invalid fields.
Do not clear valid user input after an error. Do not require the user to re-enter information unnecessarily. WCAG 2.2 specifically addresses redundant entry and accessible authentication.
Colour, Images, Motion, and Zoom
Semantic HTML and ARIA cover only part of web accessibility.
1. Colour
Do not use colour as the only way to communicate meaning. A red border alone may not tell a colour-blind user that a field contains an error. Include text, icons with accessible meaning, or another distinguishable indicator.
Text and meaningful interface elements also need sufficient contrast against their backgrounds.
2. Images
Informative images need alternative text that conveys their purpose in context. Decorative images should not create unnecessary screen-reader output. Complex charts may require an adjacent explanation or an accessible data representation rather than an unusually long alt attribute.
Let’s Build an Accessible Web Together!
We design and develop web experiences that everyone can use, simple, inclusive, and ADA-compliant.
3. Motion
Respect reduced-motion preferences and avoid unnecessary movement that could cause discomfort. Autoplaying animation should not interfere with reading or operation.
4. Zoom and reflow
Pages should remain usable when users enlarge text or zoom the browser. Content should not overlap, disappear, become clipped, or require unnecessary two-dimensional scrolling.
Responsive design is not automatically accessible. It must also accommodate magnification, text spacing changes, and browser-level overrides.
Choosing Between HTML and ARIA
| Requirement | Preferred approach |
| Page structure and landmarks | Semantic HTML |
| Standard action | Native <button> |
| Navigation | Native <a> with a valid destination |
| Visible form label | Native <label> |
| Name derived from existing visible content | aria-labelledby |
| Additional instructions or error context | aria-describedby |
| Dynamic status message | A carefully selected live region |
| Custom widget without a native equivalent | ARIA role, state, keyboard model, and focus behaviour |
| Decorative content | Appropriate native treatment or aria-hidden="true" when necessary |
The decision is not “HTML or ARIA” in every case. Strong implementations often use semantic HTML as the foundation and add a small amount of ARIA to communicate a state or relationship that HTML does not expose.
How to Test Accessibility in HTML and React
Accessibility cannot be verified through code review alone.
1. Begin with keyboard testing
Disconnect the mouse and complete the primary journeys using Tab, Shift+Tab, Enter, Space, arrow keys, and Escape where appropriate.
Check whether every control is reachable, focus is visible, the order makes sense, and no component traps the user.
2. Inspect the accessibility tree
Browser developer tools can show the computed role, name, description, and state of an element. This helps developers identify missing names, duplicate labels, incorrect roles, and unexpected ARIA precedence.
3. Use automated checks
Automated tools can detect problems such as missing labels, duplicate IDs, invalid ARIA, certain contrast failures, and some structural mistakes. They can run during development, component testing, and CI.
However, automation cannot determine whether focus moves appropriately, alternative text is meaningful, instructions are understandable, or a complete journey is usable.
4. Test with screen readers
At minimum, test critical journeys with representative browser and screen-reader combinations. Listen for control names, roles, states, headings, landmarks, errors, dynamic messages, and route changes.
Do not test only whether the screen reader “reads the page.” Test whether a user can understand and complete the task efficiently.
5. Include people with disabilities
Standards and tools are essential, but user evaluation identifies problems that technical checks may miss. Real users can reveal confusing interaction models, excessive announcements, unclear instructions, and workflows that technically pass criteria but remain difficult to use.
How Users Experience Inaccessible Interfaces
Missing accessibility changes the actual product experience.
Without semantic structure, a screen-reader user may hear a long sequence of text without useful headings or landmarks. Without a programmatic label, an input may be announced only as “edit text.” Without focus management, a modal can appear visually while keyboard focus remains behind it.
A keyboard user may encounter a clickable element that cannot receive focus. A user with low vision may lose the focused control behind a sticky header. A colour-blind user may see an error state without being able to distinguish it. A user with a motor disability may be unable to complete an interaction that depends entirely on dragging.
Accessibility work turns these hidden failures into explicit engineering requirements.
Building Accessibility into the Development Workflow
Accessibility is easier to maintain when it is included at every stage.
During design, define keyboard behaviour, focus movement, error states, accessible names, responsive layouts, and reduced-motion behaviour.
During development, start with semantic elements, use established accessible components where appropriate, and test individual components before assembling complete pages.
During review, include accessibility acceptance criteria instead of relying only on visual comparison.
During CI, run automated rules and component tests, but do not treat a passing scan as proof of conformance.
Before release, manually test the most important journeys with a keyboard, zoom, high-contrast or forced-colour settings where relevant, and representative assistive technologies.
After release, provide a way for users to report accessibility barriers and include those reports in the normal defect process.
Frequently Asked Questions
Why is semantic HTML important for accessibility?
Semantic HTML communicates structure and purpose to browsers and assistive technologies. Native interactive elements also provide established keyboard behaviour, reducing the amount of accessibility logic developers must recreate.
When should ARIA be used instead of semantic HTML?
Use ARIA when HTML cannot express a necessary role, state, property, or relationship. ARIA should enhance suitable HTML and custom widgets, not replace native elements that already provide the required semantics.
Does React make applications accessible automatically?
No. React supports semantic HTML and standard ARIA attributes, but developers remain responsible for labels, keyboard interaction, focus management, dynamic announcements, contrast, errors, and assistive-technology testing.
What is the difference between aria-labelledby and aria-describedby?
aria-labelledby defines an element’s accessible name using referenced content. aria-describedby provides supplementary information such as instructions, hints, requirements, or validation feedback.
Is accessibility good for SEO?
Semantic structure, headings, descriptive links, and meaningful alternative text can also help search engines understand content. However, accessibility is broader than SEO and should be implemented primarily so people can use the product.
Conclusion
Web accessibility is an engineering responsibility, not a final layer added after an interface is complete.
Semantic HTML provides the strongest foundation. ARIA can fill genuine semantic gaps, but it must remain synchronised with visible behaviour and be supported by the correct keyboard and focus model. React does not change these principles; it makes deliberate focus management and dynamic announcements more important because the interface can change without a page reload.
The most dependable process is straightforward: use native HTML wherever possible, build accessible behaviour into reusable components, test with more than automated tools, and include people with disabilities in evaluation.
An accessible interface is not only more inclusive. It is generally clearer, more predictable, easier to test, and more resilient across browsers, devices, and ways of interacting.



