
Web Accessibility (A11y): For Everyone
For visually impaired, for keyboard users, and for your future self. Small `alt` tag makes a big difference.

For visually impaired, for keyboard users, and for your future self. Small `alt` tag makes a big difference.
Why does my server crash? OS's desperate struggle to manage limited memory. War against Fragmentation.

Two ways to escape a maze. Spread out wide (BFS) or dig deep (DFS)? Who finds the shortest path?

A comprehensive deep dive into client-side storage. From Cookies to IndexedDB and the Cache API. We explore security best practices for JWT storage (XSS vs CSRF), performance implications of synchronous APIs, and how to build offline-first applications using Service Workers.

Fast by name. Partitioning around a Pivot. Why is it the standard library choice despite O(N²) worst case?

One day my mouse battery died, and I tried using my own website with just the keyboard. I pressed Tab but couldn't see where the focus went. By the third click, I was just guessing. It took me a full minute to reach the "Login" button. That's when I realized: I couldn't use my own site.
Web accessibility (shortened to A11y - "a" + 11 letters + "y") wasn't some distant concern. It was my problem. And it wasn't just for people with disabilities. It was for people with broken arms, people using phones on the subway with one hand, my parents with aging eyes, and most importantly, my future self.
This isn't a "Web Accessibility Guide". It's my notes from learning through trial and error, from banging my head against code until things clicked.
When I first built websites, I made buttons like this:
<!-- Bad example: div button -->
<div class="btn" onclick="handleClick()">
Click me
</div>
It worked. It triggered the function when clicked. But it wasn't keyboard accessible. No matter how many times I pressed Tab, this "button" never received focus. When I turned on a screen reader, it just said "Click me" - no indication it was clickable at all.
And I added images like this:
<!-- Bad example: image without alt -->
<img src="/images/product.jpg">
When the image didn't load, there was just empty space. The screen reader said "image" - but what image? Nobody knew.
These two mistakes made my site inaccessible. When I realized it, I felt a chill down my spine.
The solution was surprisingly simple. Semantic HTML - using meaningful tags. Buttons with <button>, images with alt, navigation with <nav>, main content with <main>. Following this principle solved 80% of the problems.
<!-- Good example: button tag -->
<button onclick="handleClick()">
Click me
</button>
After this change, pressing Tab automatically moved focus to the button. Enter triggered the click. The screen reader announced "Click me, button" - perfectly clear. Everything just worked, like magic.
<!-- Good example: alt attribute -->
<img src="/images/product.jpg" alt="Red sneakers product photo">
Adding alt meant that when the image didn't load, users saw "Red sneakers product photo" as text. Screen readers read the same thing. Users understood what the image was supposed to show.
I realized the essence of web accessibility is "telling the browser what things mean". A div is just a box, but a button tells the browser "this is clickable". That difference was everything.
WCAG (Web Content Accessibility Guidelines) is the W3C's web accessibility standard. I came to see it as an "accessibility checklist". There are four main principles:
Translating these principles into code gives you semantic HTML structure - dividing the page into meaningful sections.
<!-- Good example: semantic HTML structure -->
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>What is Web Accessibility?</h1>
<section>
<h2>Basic Concepts</h2>
<p>A web everyone can use...</p>
</section>
</article>
</main>
<footer>
<p>© 2025 My Site</p>
</footer>
With this structure, screen readers announced "header region", "main content", "footer". Users could jump directly to the section they wanted, like using a table of contents.
The bad example looked like this:
<!-- Bad example: all divs -->
<div class="header">
<div class="nav">
<div class="link">Home</div>
<div class="link">About</div>
</div>
</div>
<div class="content">
<div class="title">What is Web Accessibility?</div>
<div class="text">A web everyone can use...</div>
</div>
Screen readers just read "div, div, div...". No way to tell what was header and what was content. It looked pretty with CSS, but the meaning was lost.
I found this metaphor helpful: Semantic HTML is like putting signs in a building. "Restroom", "Emergency Exit", "Elevator". Without signs, you get lost in the building. Divs are blank walls without signs, while header/nav/main/footer are clear signs.
Sometimes semantic HTML isn't enough. Tabs, modals, dropdowns - these don't map to basic HTML tags. That's when you use ARIA (Accessible Rich Internet Applications).
ARIA has three parts:
role="dialog", role="tab".aria-label="Close button", aria-describedby="help-text".aria-expanded="false", aria-hidden="true".Here's a modal example:
<!-- Good example: ARIA for modal -->
<div role="dialog" aria-labelledby="modal-title" aria-modal="true">
<h2 id="modal-title">Login</h2>
<form>
<label for="email">Email</label>
<input type="email" id="email" aria-required="true">
<button type="submit">Submit</button>
</form>
<button aria-label="Close modal" onclick="closeModal()">
×
</button>
</div>
role="dialog" says "this is a modal". aria-modal="true" says "everything outside this modal is now inactive". aria-labelledby="modal-title" connects to the modal's title. Screen readers announce "Login dialog opened".
But ARIA is a double-edged sword. Used wrong, it makes accessibility worse. There's a saying: "No ARIA is better than Bad ARIA". For example, don't do this:
<!-- Bad example: unnecessary ARIA -->
<button role="button" aria-label="button">
Click me
</button>
<button> already has the button role. No need to add role="button". aria-label="button" is also redundant - the button already has "Click me" text. Overusing ARIA like this makes screen readers say "button, button, Click me, button" three times.
Here's how I summarized it: Only use ARIA when HTML isn't enough. If HTML solves it, you don't need ARIA.
Imagine using a site without a mouse. Navigate with Tab, click with Enter or Space, close with Escape. That's keyboard navigation.
The most important part is focus management. Focus is the visual indicator saying "you are here". Usually a blue outline. Never remove it with CSS.
/* Bad example: removing focus outline */
button:focus {
outline: none;
}
This leaves keyboard users not knowing where they are. Do this instead:
/* Good example: custom focus style */
button:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
Instead of removing the outline, customize it to look better. Users can clearly see where focus is.
Tab order is also important. When you press Tab, focus should move in a logical sequence. Usually follows HTML order. You can change it with tabindex, but be careful.
<!-- Bad example: confusing tabindex -->
<button tabindex="3">First</button>
<button tabindex="1">Second</button>
<button tabindex="2">Third</button>
This makes Tab go "Second → Third → First". Confusing. Better to not use tabindex and just structure your HTML logically.
However, tabindex="-1" is useful. It allows programmatic focus while preventing Tab key access. Used for moving focus into a modal when it opens.
// Good example: moving focus when opening modal
function openModal() {
const modal = document.getElementById('modal');
modal.style.display = 'block';
modal.setAttribute('aria-hidden', 'false');
const firstInput = modal.querySelector('input');
firstInput.focus(); // Move focus to first input in modal
}
I also learned about skip links. Tabbing through long navigation every time is tedious. A hidden "Skip to main content" link at the top lets keyboard users jump straight to content.
<!-- Good example: skip link -->
<a href="#main-content" class="skip-link">
Skip to main content
</a>
<!-- ... long navigation ... -->
<main id="main-content">
<h1>Main Content</h1>
</main>
/* Skip link hidden by default, visible on focus */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
First Tab shows the "Skip to main content" link. Press Enter and you jump straight to content. Super convenient.
Color contrast was important too. Light gray text on white background is hard to read for people with poor vision. WCAG AA standard is 4.5:1 (body text), AAA is 7:1.
I ran Chrome DevTools Lighthouse. Got an accessibility score of 70, with warnings about "insufficient text and background color contrast". Gray button text was the problem.
/* Bad example: insufficient contrast */
button {
background: #e0e0e0;
color: #999999; /* Contrast ratio 2.8:1 - fails */
}
Changed it to this:
/* Good example: sufficient contrast */
button {
background: #e0e0e0;
color: #333333; /* Contrast ratio 7.2:1 - AAA pass */
}
Darker text made it much easier to read. My eyes were less tired without me even realizing it.
Form accessibility was another lesson. Input fields must have connected <label> tags.
<!-- Bad example: input without label -->
<input type="text" placeholder="Enter your name">
With only placeholder, screen readers say "text input, blank". They don't know what input this is.
<!-- Good example: connected label -->
<label for="name">Name</label>
<input type="text" id="name" placeholder="Enter your name">
Connecting with for and id made screen readers say "Name, text input". Also, clicking the label focuses the input. Super convenient on mobile.
Error messages need to be clear too.
<!-- Bad example: unclear error message -->
<input type="email" id="email">
<span style="color: red;">Error</span>
Just showing "Error" doesn't tell you what's wrong. Change it to this:
<!-- Good example: clear error message -->
<label for="email">Email</label>
<input type="email" id="email" aria-invalid="true" aria-describedby="email-error">
<span id="email-error" role="alert">
Invalid email format. Example: user@example.com
</span>
aria-invalid="true" announces "this field has an error". aria-describedby connects the error message. role="alert" makes screen readers read the error immediately. Users know exactly what to fix.
Theory learned, time for practice. I built an accessible dropdown menu.
<!-- Accessible dropdown menu -->
<nav>
<ul role="menubar">
<li role="none">
<button
aria-haspopup="true"
aria-expanded="false"
aria-controls="dropdown-menu"
id="menu-button"
>
Menu
</button>
<ul role="menu" id="dropdown-menu" hidden>
<li role="none">
<a href="/profile" role="menuitem">Profile</a>
</li>
<li role="none">
<a href="/settings" role="menuitem">Settings</a>
</li>
<li role="none">
<a href="/logout" role="menuitem">Logout</a>
</li>
</ul>
</li>
</ul>
</nav>
// Dropdown keyboard controls
const menuButton = document.getElementById('menu-button');
const dropdownMenu = document.getElementById('dropdown-menu');
menuButton.addEventListener('click', toggleMenu);
menuButton.addEventListener('keydown', handleKeydown);
function toggleMenu() {
const isExpanded = menuButton.getAttribute('aria-expanded') === 'true';
menuButton.setAttribute('aria-expanded', !isExpanded);
dropdownMenu.hidden = isExpanded;
if (!isExpanded) {
// Focus first item when opening menu
dropdownMenu.querySelector('[role="menuitem"]').focus();
}
}
function handleKeydown(e) {
if (e.key === 'Escape') {
toggleMenu();
menuButton.focus(); // Return focus to button when closing
}
}
With this implementation:
For the first time, I felt "this is actually accessible".
Testing is essential after building. I used three tools:
First Lighthouse run gave me 68 points. Main issues:
Fixed them one by one and retested - got 94 points. Not perfect, but satisfying.
For screen reader testing, I closed my eyes. Turned on VoiceOver and tried using my site with keyboard only. Got to login in 30 seconds. Compared to the previous 1 minute, that's 50% improvement. All buttons were clearly announced, form inputs were manageable.
When I first started learning web accessibility, I thought "this is too complicated". ARIA attributes had so much to memorize, WCAG guidelines were dry. But after actually doing it, I realized the core was simple: "Don't give others a site you can't use yourself".
Try using it without a mouse and the answers appear. Turn on a screen reader and the gaps show up. Run Lighthouse and you get a score. The tools are plentiful, the methods are clear. The problem was just doing it.
Now when I make a button, I use <button>. I add alt to images. I never remove focus outlines. I connect <label> to forms. These small habits accumulate to create "a web everyone can use", and I believe that.
I loved this final metaphor: Web accessibility is a ramp. Built for wheelchair users, but it helps parents with strollers, travelers with luggage, injured people - everyone. One alt tag, one <button> tag becomes someone's ramp. And someday it'll be my ramp. That's how I came to understand it.