I have watched developers ship beautiful interfaces that are completely unusable with a keyboard. I have seen modal dialogs trap screen reader users in infinite loops. I have tested forms where the error messages were invisible to assistive technology. Accessibility is not a checkbox at the end of a project. It is the foundation of everything you build.
Accessibility is not about disability. It is about designing for the full spectrum of human capability.
Semantic HTML is 80% of accessibility. A button should be a button element, not a div with an onclick handler. A heading should be an h1-h6, not a styled span. Screen readers navigate by landmark regions and heading hierarchy. When you use div soup, you strip away the map that assistive technology needs to understand your page.
<!-- WRONG: completely inaccessible -->
<div class="btn" onclick="submit()">Submit</div>
<!-- CORRECT: native semantics + keyboard support -->
<button type="submit">Submit</button>
<!-- WRONG: fake heading -->
<span class="heading-large">Dashboard</span>
<!-- CORRECT: proper heading hierarchy -->
<h1>Dashboard</h1>
<section aria-labelledby="recent-heading">
<h2 id="recent-heading">Recent Activity</h2>
</section>ARIA is a scalpel, not a hammer. Use it only when HTML semantics are insufficient. aria-expanded on a custom dropdown. aria-live for dynamic content updates. aria-describedby to link form errors to their inputs. Every ARIA attribute you add is a contract with assistive technology. Break that contract and you actively harm users.
<!-- Custom dropdown with proper ARIA -->
<button aria-haspopup="listbox"
aria-expanded="false"
aria-controls="dropdown-list"
id="dropdown-btn">
Select Option
</button>
<ul id="dropdown-list"
role="listbox"
aria-labelledby="dropdown-btn"
hidden>
<li role="option" aria-selected="true" tabindex="0">Option 1</li>
<li role="option" aria-selected="false" tabindex="-1">Option 2</li>
</ul>
<!-- Form error linking -->
<input type="email"
aria-invalid="true"
aria-describedby="email-error"
id="email">
<span id="email-error" role="alert">
Please enter a valid email address.
</span>Focus management is where most accessibility failures happen. When a modal opens, focus must move to the modal. When it closes, focus must return to the trigger. Tab must cycle within the modal, not escape to the background page. This is not optional behavior. It is the minimum viable accessibility for any interactive component.
// Modal focus trap
class Modal {
constructor(element) {
this.element = element;
this.trigger = null;
this.focusable = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
}
open(trigger) {
this.trigger = trigger;
this.element.hidden = false;
this.element.querySelector(this.focusable)?.focus();
document.addEventListener('keydown', this.handleKeydown);
}
close() {
this.element.hidden = true;
this.trigger?.focus();
document.removeEventListener('keydown', this.handleKeydown);
}
handleKeydown = (e) => {
if (e.key !== 'Tab') return;
const focusable = [...this.element.querySelectorAll(this.focusable)];
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
};
}Color contrast is not negotiable. WCAG AA requires 4.5:1 for normal text and 3:1 for large text. That trendy light gray on white background? It fails. That subtle error state with slightly redder text? It fails. Use tools. Test with actual screen readers. NVDA on Windows, VoiceOver on macOS, TalkBack on Android. Automated tools catch 30% of issues. The other 70% require human testing. Do the work.
Accessibility is not charity. It is good engineering. Keyboard navigation benefits power users. Screen reader support benefits search engines. High contrast benefits users in bright sunlight. Captions benefit people watching videos in silent environments. When you build accessibly, you build better software for everyone. That is not a side effect. That is the point.
Comments