The Ticket That Ananya Couldn't Book
Suppose you and your friend Ananya are both trying to book an IPL match ticket online at 9:00 PM, the exact minute sales open. You use a mouse, tap the seat you want, and pay through UPI in under a minute. Ananya is blind. She uses her phone's screen reader, TalkBack, which reads the screen aloud and lets her move between items by swiping instead of tapping randomly. On the same page, TalkBack reaches the seat map and then goes silent. It says "double tap to activate" on a plain gray rectangle, but nothing tells her which seat that rectangle represents, or whether it is even available. She swipes past twenty identical, unlabelled rectangles and gives up.
Nothing was "broken" in the way a bug is usually broken. The page loaded fine, looked fine, and every visual sighted user tested it happily. The problem is that the developer built the seat map using twenty plain <div> elements, coloured with CSS and wired up with a mouse-click handler in JavaScript. A sighted mouse user sees the colour and the seat number printed inside the div. A screen reader, which cannot see colour or layout, has almost nothing to describe, because a <div> carries no built-in meaning at all. This chapter is about exactly that gap: the difference between a website that merely looks usable and one that is actually usable by the wide range of people who will load it, and the concrete techniques — HTML, a little CSS math, and a few rules of thumb — that close that gap.
What "Accessibility" Really Means
Web accessibility means designing and coding a website so that people with disabilities can perceive it, navigate it, and use it, ideally to the same standard as everyone else. It is useful to break "disability" into four broad categories, because each one breaks a website in a different way:
- Visual — blindness, low vision, or colour blindness. Screen readers convert text to speech or braille; but they can only read what the HTML actually says, and colour-blind users cannot rely on colour alone to tell states apart (for example, a red-vs-green "sold out" indicator with no text).
- Auditory — deafness or hearing loss. A video's audio explanation is useless without captions or a transcript.
- Motor — conditions like cerebral palsy, arthritis, or a temporarily broken wrist that make precise mouse movement difficult or impossible. These users often navigate entirely with a keyboard, a switch device, or voice control.
- Cognitive — dyslexia, ADHD, or memory-related conditions that make dense text, inconsistent layouts, or unexplained jargon much harder to process.
According to Census 2011, roughly 2.68 crore people in India — about 2.2% of the population — reported living with some form of disability, and disability-rights organisations generally consider this an undercount, since many conditions go unreported in a household survey. That is already a huge number of potential users. But the categories above are not fixed groups of "other people" — they describe situations every one of us passes through. A cricket fan watching match highlights on mute in a crowded train car needs captions just as much as a deaf viewer does. Someone typing with one hand because the other is holding a chai cup needs full keyboard support just as much as someone with a motor disability does. This is called the curb-cut effect, named after the sloped cuts in pavements originally built for wheelchair users, which turned out to help parents with prams, delivery workers with trolleys, and cyclists just as much. Accessible design is rarely a feature for a small minority — it is usually just better design for everyone, revealed by thinking hard about the hardest case first.
Common misconception: "Accessibility only matters for blind users, so as long as I add some alt text I'm done." This is wrong on two counts. First, as the list above shows, blindness is only one of at least four major categories of disability a website can fail to serve. Second, even within "supporting blind users," alt text alone does not help someone who cannot use a mouse, cannot see low-contrast text, or gets lost in an inconsistent layout. Accessibility is a set of related but distinct techniques, not a single checkbox.
POUR: The Four Rules Behind Every Accessible Page
The World Wide Web Consortium (W3C) — the organisation that maintains the standards for how the web is built — publishes the Web Content Accessibility Guidelines, universally shortened to WCAG. Rather than a giant list of arbitrary rules, WCAG organises everything under four principles, easy to remember by the acronym POUR:
- Perceivable — information must be presented in a way every user's senses can register. Text alternatives for images, captions for audio, and sufficient colour contrast all fall here.
- Operable — every interactive element must be usable without a mouse, and users must have enough time to read and act (no content that vanishes before it can be read, no keyboard traps that swallow the Tab key).
- Understandable — text should be readable, and pages should behave predictably (a button labelled "Next" should not silently submit a payment).
- Robust — content must be coded so that a wide range of tools, including current and future assistive technology, can interpret it reliably. This is largely a matter of using HTML correctly rather than inventing your own tag soup.
Every technique in the rest of this chapter is really just one of these four principles applied to a specific HTML or CSS decision. Keep POUR in the back of your mind — when you are unsure whether something is an accessibility problem, ask which of the four letters it violates.
Semantic HTML: What the Browser Hands to a Screen Reader
Here is the single most important idea in this whole chapter: a screen reader does not "see" your page the way you do. It does not read your CSS, and it does not know that a rectangle styled to look like a button is "supposed to be" a button. Instead, the browser builds a separate, invisible data structure called the accessibility tree directly from your HTML tags, and the screen reader reads only from that tree. If your HTML does not say "this is a button," the accessibility tree will not say so either, no matter how convincing the CSS looks.
This is exactly what went wrong with Ananya's seat map. Compare these two ways of coding the same visible button:
<!-- Div soup: looks like a button, is not one -->
<div class="seat-button" onclick="bookSeat(14)">
Seat 14
</div>
<!-- Semantic HTML: actually is a button -->
<button onclick="bookSeat(14)">
Seat 14
</button>
Both versions can be made to look pixel-for-pixel identical with CSS. But the <div> version has no role in the accessibility tree at all — TalkBack or NVDA (a free screen reader for Windows) will either stay silent or, at best, announce it as unlabelled "clickable text." It also cannot be reached by pressing the Tab key, because plain <div> elements are not part of the keyboard tab order by default. Pressing Enter or Space on it, once somehow focused, does nothing, because only real form controls get that behaviour built in. The <button> version, by contrast, is automatically announced with the role "button" and its visible text as its accessible name, automatically included in the Tab order, and automatically activated by both Enter and Space — all without a single line of extra JavaScript. The diagram below shows what actually reaches the accessibility tree in each case.
The same logic extends to every other tag: a heading must be a real <h1>–<h6>, not a <span> made to look big and bold with CSS, because screen reader users very commonly jump between headings to skim a page, and that shortcut only works on real heading tags. A list of links must be a real <ul>/<li> structure, not a row of <span>s separated by spaces, because the accessibility tree announces "list, 5 items" so a user knows how much content to expect. The general rule: reach for the HTML tag whose built-in meaning matches what the element does, and let the browser do the accessibility work for free. Only build a custom widget from generic tags when no native tag exists for what you need — and even then, as you'll see, you must add the missing information back in yourself.
Alt Text: Describing Images to People Who Cannot See Them
The alt attribute on an <img> tag is read aloud by a screen reader in place of the image. Writing good alt text means asking "what information is this image conveying in this context?" — not "what does this image literally contain?"
<!-- Bad: describes pixels, not meaning -->
<img src="rangoli7.jpg" alt="image123.jpg">
<!-- Bad: no information at all -->
<img src="rangoli7.jpg" alt="image">
<!-- Good: conveys what a sighted user would take away -->
<img src="rangoli7.jpg"
alt="A peacock-shaped rangoli in red and yellow powder at the school's Diwali entrance">
<!-- Good: purely decorative image, correctly silenced -->
<img src="border-swirl.png" alt="">
Notice the last example. Leaving alt="" (an empty string, not a missing attribute) is deliberate and correct for purely decorative images — a screen reader will skip it silently instead of wasting the user's time announcing "border swirl image" for a graphic that carries no actual information. Omitting the alt attribute entirely is different and worse: some screen readers fall back to reading the image's file name, which is exactly the "image123.jpg" problem above. The rule of thumb: every <img> needs an alt attribute; whether that text is empty or descriptive depends on whether the image carries meaning.
The Arithmetic of Colour Contrast
Low-vision users and colour-blind users (roughly 1 in 12 men worldwide have some form of colour blindness, most commonly red-green) need enough brightness difference between text and its background to read comfortably — regardless of the actual hues chosen. WCAG turns "enough difference" into an exact number you can calculate, called the contrast ratio. Here is how it works, step by step.
Step 1: convert each colour to a relative luminance, a single number from 0 (pure black) to 1 (pure white) that represents how bright the colour appears to the human eye. The formula weights red, green and blue unequally, because our eyes are most sensitive to green light and least sensitive to blue:
L = 0.2126 x R + 0.7152 x G + 0.0722 x B
(R, G, B here are each channel's 0–255 value converted to a 0–1 scale and passed through a small brightness-correction curve first — the exact curve matters for precision tools, but the formula above is the part you should remember: green counts for roughly 70% of perceived brightness, red about 21%, and blue barely 7%.)
Step 2: once you have the relative luminance of the lighter colour (L1) and the darker colour (L2), the contrast ratio is simply:
Contrast ratio = (L1 + 0.05) / (L2 + 0.05)
Let's compute two real, worked examples that web designers argue about constantly: grey text on a white background. Pure white has relative luminance L = 1.0 exactly, since every channel is already at maximum brightness.
Example A — is #767676 grey text on white readable? Running the hex value 118/255 through the luminance formula gives a relative luminance of about L = 0.181 for this grey. Plugging into the ratio formula:
Contrast ratio = (1.0 + 0.05) / (0.181 + 0.05)
= 1.05 / 0.231
= 4.54
WCAG's AA standard requires a contrast ratio of at least 4.5:1 for ordinary body text. Our grey scores 4.54:1 — it clears the bar, just barely. This is not a coincidence: #767676 is the darkest, most "washed out"-looking grey that still legally passes AA on a white background, which is exactly why you'll spot it as the default secondary-text colour on a huge number of professional websites.
Example B — what about the slightly lighter #999999, which many students pick because it looks "cleaner"? Its relative luminance works out to about L = 0.319. Then:
Contrast ratio = (1.0 + 0.05) / (0.319 + 0.05)
= 1.05 / 0.369
= 2.85
A ratio of 2.85:1 fails the 4.5:1 requirement outright — this text would be legitimately hard to read for anyone with low vision, and noticeably harder to read for everyone else too, even though the difference between #767676 and #999999 looks tiny on screen. This is exactly why "it looks fine to me" is not a reliable accessibility test: two greys that appear almost identical can be on opposite sides of the readability threshold, and only the arithmetic tells you which side you're on. (Large text, 18pt or larger — or 14pt bold — gets a relaxed minimum of 3:1, since bigger letterforms remain legible at lower contrast.)
In practice you will not compute relative luminance by hand every time — free tools like the WebAIM Contrast Checker do the arithmetic instantly — but understanding the formula tells you why the tool gives the verdict it does, and why "just eyeball it" is not good enough.
Keyboard Navigation: When There Is No Mouse
Press the Tab key right now on any well-built website. You should see a visible outline — a "focus ring" — jump from link to link, button to button, in the order they appear in the HTML. Every motor-disabled user who cannot operate a mouse relies entirely on this behaviour, together with Enter, Space, and arrow keys, to use the whole page.
Native HTML elements — <button>, <a href="...">, <input>, <select> — get correct keyboard behaviour automatically. Custom widgets built from generic tags do not, and this is one of the most common accessibility bugs in real websites, including large commercial ones. Consider a dropdown menu built like this:
<span class="dropdown-trigger" onclick="toggleMenu()">
Select City
</span>
A mouse user clicks it and the menu opens. A keyboard-only user tabs toward it, and the browser's focus jumps straight past the <span> to whatever comes after it, because <span>, like <div>, is not focusable by default and has no click-via-keyboard behaviour built in. The keyboard user cannot open the menu at all — not "with difficulty," but literally cannot, no matter how many times they press Tab.
The correct fix is usually to use a real <button> in the first place. If a project genuinely requires a non-native element (say, a complex custom widget), the developer must manually restore the three things the browser would otherwise have given for free: add tabindex="0" so the element enters the tab order, add a keydown event listener that checks for the Enter and Space keys and calls the same function the click handler calls, and add an appropriate ARIA role so assistive technology knows what the element is meant to be — which brings us to ARIA itself.
ARIA: The Escape Hatch, Not the First Tool
ARIA (Accessible Rich Internet Applications) is a set of HTML attributes — role, aria-label, aria-hidden, and many others — that let you attach accessibility information to elements that don't have it naturally. The single most important rule to remember, sometimes called the "First Rule of ARIA," is: if a native HTML element or attribute already has the semantics you need, use it instead of re-creating it with ARIA. ARIA is a patch for gaps that native HTML cannot fill, not a replacement for native HTML.
A good, appropriate use of ARIA is labelling an icon-only button, where the visible content gives a screen reader nothing to read:
<button aria-label="Search">
🔍
</button>
Here, a sighted user sees a magnifying-glass icon and understands its purpose instantly; a screen reader user hears "Search, button" instead of silence or "magnifying glass emoji, button." The aria-label attribute overrides what would otherwise be announced, purely for accessibility purposes — it has no visual effect at all. Misusing ARIA is a real risk too: adding role="button" to a <div> without also adding keyboard support, as shown in the previous section, actually makes things worse than doing nothing, because it tells a screen reader user "this behaves like a button" when Tab and Enter do not, in fact, work — a broken promise is worse than no promise. This is why accessibility specialists say "no ARIA is better than bad ARIA."
Forms: Labels Are Not Decoration
Compare two ways of building the same-looking input field for, say, a school admission form asking for a student's name:
<!-- Bad: disappears, and isn't reliably
announced as the field's name by every screen reader -->
<input type="text" ="Full Name">
<!-- Good: a real, permanently associated label -->
<label for="student-name">Full Name</label>
<input type="text" id="student-name">
The for attribute on <label> must exactly match the id on the <input>; this connection is what lets a screen reader announce "Full Name, edit text" the moment the field receives focus, and it also lets a sighted mouse user click the label text itself to jump the cursor into the field — useful for anyone with limited fine motor control aiming for a small checkbox or radio button. A is not a substitute: it visually vanishes the instant the user starts typing, so anyone who looks away and back loses the field's identity, and because it is meant for example text (like "e.g. Priya Sharma") rather than the field's name, screen reader behaviour around placeholders is inconsistent. Never rely on a as your only label.
Sound and Video: Captions and Transcripts
A deaf or hard-of-hearing user gets no information from an audio track at all, and a hearing user in a quiet library or a loud train platform is functionally in the same position. Two techniques cover this: captions, synchronised text shown on top of a video (using the <track kind="captions"> element inside <video>), and a full transcript, a plain-text copy of everything spoken, provided as a link near the video. Captions serve people watching in real time; transcripts additionally let a screen reader user or a search engine access the content at all, since neither can "watch" a video.
How to Test What You Build
You do not need special equipment to catch most of the mistakes in this chapter. Three checks, done in order, catch the majority of real problems:
- Unplug the mouse, literally or mentally. Navigate your entire page using only Tab, Shift+Tab, Enter, and arrow keys. If you cannot reach or activate something, a motor-disabled user cannot either.
- Turn on a screen reader for five minutes. Android phones (common across India) have TalkBack built into Settings > Accessibility at no cost; iPhones have VoiceOver. Close your eyes and try to complete one task, like submitting a form.
- Run a contrast checker on your colour choices rather than trusting your eyes, exactly as the arithmetic in the earlier section demonstrated two visually similar greys can land on opposite sides of the pass/fail line.
In India, this is not purely a matter of good practice: the Rights of Persons with Disabilities Act, 2016 requires government and public-sector websites to meet published accessibility guidelines, which is why official portals are expected to follow WCAG-based standards rather than treating accessibility as optional polish.
Common Misconceptions, Corrected
- "Accessibility means an ugly, text-only website." False — every technique above (semantic tags, sufficient contrast, keyboard support, real labels) can be applied to any visual design. Accessibility constrains how you build a page, almost never how it looks.
- "I'll add accessibility at the end, once the design is finalised." Retrofitting is possible but far more expensive, because layout decisions (like building every interactive element out of
<div>s for maximum styling flexibility) often have to be undone. Choosing semantic tags from the first line of code costs nothing extra. - "Adding ARIA attributes everywhere makes a site more accessible." As shown above, ARIA without matching behaviour actively misleads assistive technology. More ARIA is not automatically better; correct, minimal ARIA on top of correct native HTML is the goal.
Practice: Check Your Understanding
- A page uses
<span onclick="like()">❤ Like</span>for a "Like" button. Name two specific ways this fails a keyboard-only user, and rewrite it correctly. - Compute the contrast ratio of pure black text (
#000000, L = 0) on a white background (L = 1.0). Does it pass the AA large-text minimum, the AA normal-text minimum, or both? - A product photo on a shopping site has
alt="DSC04521.jpg". Explain why this is worse than no alt attribute at all, and write a better one for a photo of a red cotton kurta. - Why does
<input ="Email">without a<label>cause problems that a sighted user might never notice? - A developer adds
role="button"to a<div>but does not add akeydownhandler ortabindex. Is this an improvement over doing nothing? Justify your answer using the idea of a "broken promise" to assistive technology.
Answers: (1) It is not in the Tab order and Enter/Space do not trigger it — use <button onclick="like()">❤ Like</button>. (2) Ratio = (1.0+0.05)/(0+0.05) = 21:1, the maximum possible contrast, which clears both the 3:1 and 4.5:1 thresholds comfortably. (3) A screen reader may read the literal filename aloud, giving zero useful information, whereas alt="A red cotton kurta with mirror-work embroidery on the collar" actually helps a buyer decide; a missing attribute sometimes falls back more gracefully depending on the tool, but the safest practice is always to supply a real description. (4) The vanishes once typing begins and is not treated the same as a persistent field name, so a screen reader user who looks away, or any user who has to double back to check what a field wants, loses that information — a sighted user glancing at a full form for the first time rarely notices, because their eyes already learned the field before they started typing. (5) No — it is arguably worse, because it now announces "button" to a screen reader user, who reasonably expects Tab and Enter to work, and is then confused when they silently do not; correct behaviour requires role, tabindex, and a keydown handler together, or simply using <button>.
Summary
Web accessibility means building pages that people with visual, auditory, motor, and cognitive disabilities can actually use — and, through the curb-cut effect, that end up working better for everyone else too. WCAG organises the whole field under four principles: Perceivable, Operable, Understandable, and Robust (POUR). In practice, this comes down to a handful of concrete habits: choose semantic HTML tags (<button>, real headings, real lists) so the browser's accessibility tree carries the correct information automatically; write alt text that conveys meaning rather than file names, using an empty alt="" only for genuinely decorative images; check colour contrast using the relative-luminance formula L = 0.2126R + 0.7152G + 0.0722B and the ratio (L1+0.05)/(L2+0.05), remembering that WCAG AA requires at least 4.5:1 for normal text and 3:1 for large text; make sure every interactive element works with only a keyboard; use ARIA only to fill genuine gaps in native HTML, never as a first resort or a substitute for matching keyboard behaviour; and connect every form field to a real <label> rather than relying on a . Test what you build the way Ananya would experience it — mouse unplugged, screen reader on — rather than trusting how it merely looks.
Think About It
Think about this: How would you explain web accessibility: building for everyone to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.