AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Accessibility: Building Inclusive Web Apps

📚 Frontend⏱️ 22 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 22 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

The Button That Isn't a Button

Open any code editor and type this line, then load it in a browser:

<div onclick="submitForm()" style="background:#1a73e8;color:white;padding:10px 20px;border-radius:6px;">
  Submit
</div>

It looks exactly like a button. It has a blue background, rounded corners, white text, and clicking it runs submitForm(). Visually, nothing is wrong. Now imagine a student named Aditi who is blind and uses a phone with a screen reader (the free tool TalkBack on Android or VoiceOver on iPhone), which reads the page aloud instead of displaying it. She swipes right to move between elements on the page. When she reaches this "button," the screen reader says nothing useful — at best it reads the word "Submit" as plain, non-interactive text, exactly the way it would read a paragraph. It does not say "button." It does not tell her she can activate it. And if she tries to reach it using the Tab key on a keyboard instead of a mouse, her cursor skips right over it, because a <div> is never part of the keyboard-navigation order by default.

The form is completely unusable for her, even though every pixel of it renders correctly. This is the core problem this chapter solves: a web page can be visually perfect and functionally invisible at the same time, because sighted, mouse-using people and screen-reader or keyboard-only users experience two different versions of your page — the pixels, and something called the accessibility tree. If you only ever test the pixels, you have only tested half your app.

What "Accessible" Actually Means

Web accessibility means building apps that people with a range of abilities can perceive, understand, and operate. That includes people who are blind or have low vision, people who are colour-blind, people with motor disabilities who cannot use a mouse precisely (or at all), people who are deaf or hard of hearing, and people with cognitive or attention differences. It also quietly helps people with no disability at all: someone browsing in bright Indian summer sunlight where the phone screen is hard to read, someone with a cracked touchscreen who navigates by keyboard, or someone whose earphones just died and now needs captions.

The international standard that defines accessibility rules is the Web Content Accessibility Guidelines (WCAG), published by the W3C. WCAG organises everything around four principles, often remembered by the acronym POUR:

  • Perceivable — information must be presented in a way users can perceive. A screen-reader user cannot perceive an image with no text description; a colour-blind user cannot perceive information conveyed only through colour.
  • Operable — users must be able to operate every control. If a feature only works with a mouse hover, a keyboard-only user cannot operate it.
  • Understandable — content and interface behaviour must be predictable and clear. An error message that just says "Invalid input" without saying which field or why is not understandable.
  • Robust — content must work correctly with current and future assistive technologies (screen readers, braille displays, voice control), which is exactly why using correct, standard HTML matters so much.

WCAG also defines three conformance levels: A (minimum), AA (the level almost every real organisation targets, and the level Indian and international accessibility laws typically reference), and AAA (the strictest, often impractical for an entire site). Everything you will compute in this chapter targets level AA, because that is the practical, industry-standard bar.

Semantic HTML: Giving the Browser a Skeleton It Can Read Aloud

Every time a browser loads a page, it builds two trees from your HTML, not one. The first is the familiar DOM tree — the nested structure of elements you already know from JavaScript. The second, built alongside it automatically, is the accessibility tree. For each element, the browser computes three things a screen reader will use: its role (what kind of thing is it — button, heading, link, checkbox?), its accessible name (what should be announced — usually the visible text or a label), and its state (is it checked, expanded, disabled, focused?).

Here is the critical fact: the browser computes the role automatically only for elements whose tag already carries meaning. Compare these two buttons:

<!-- Version A: a div pretending to be a button -->
<div onclick="addToCart()">Add to Cart</div>

<!-- Version B: an actual button element -->
<button onclick="addToCart()">Add to Cart</button>

For Version A, the accessibility tree entry is: role = generic (essentially "nothing special"), not focusable, no keyboard behaviour. For Version B, the browser fills in role = button, accessible name = "Add to Cart", and automatically makes it reachable by Tab, activatable by both Enter and Spacebar, and announced by every screen reader as "Add to Cart, button" — all without you writing a single extra line of code. You get this entire bundle of behaviour free, purely by choosing the tag that matches what the element actually is. This is the single most important idea in accessibility: use the HTML element whose built-in meaning matches your intent, and the browser does the accessibility work for you.

The same logic applies everywhere: use <nav> for navigation blocks instead of <div class="nav">, use <h1> through <h6> for real headings in order (screen-reader users often jump between headings the way sighted users skim a page, and a screen reader can generate a full "table of contents" from correctly nested headings), use <label> tied to form inputs instead of a plain nearby <span>, and use <ul>/<ol> for lists so a screen reader can announce "list, 4 items" and let the user know how much content is coming.

NeedWrong tagRight tag
Clickable action<div onclick><button>
Link to another page<span onclick><a href="...">
Form field label<span>Name</span><label for="name">Name</label>
Section heading<div class="bigtext"><h2>

✅ Using the right tag: automatic role, name, keyboard support. ❌ Using a generic <div> or <span> for interactive content: none of that comes for free, and you would have to rebuild it by hand.

Colour Contrast: The Arithmetic Behind Readable Text

Suppose you are styling body text for a school announcements page and, wanting something softer than pure black, you pick a mid-grey. Which grey is "too light"? This is not a matter of taste — WCAG defines it with an exact number, and you can compute it yourself with arithmetic you already know.

Every colour has a relative luminance, a number between 0 (pure black) and 1 (pure white) that represents how much light it reflects, adjusted for how the human eye perceives brightness (our eyes are more sensitive to green light than red or blue, and less sensitive to changes among dark colours than among light ones). For a grey colour, where the red, green, and blue channels are all equal, the calculation has two steps:

  1. Normalise: take the 0–255 channel value and divide by 255, giving a fraction s between 0 and 1.
  2. Linearise (gamma-correct): screens do not display brightness in a straight line with the numbers we feed them, so we correct for that curve:
    if s <= 0.03928:
        L = s / 12.92
    else:
        L = ((s + 0.055) / 1.055) ** 2.4
    

Once you have the luminance L1 of the lighter colour and L2 of the darker colour, the contrast ratio between them is:

contrast = (L1 + 0.05) / (L2 + 0.05)

The ratio ranges from 1:1 (identical colours, no contrast at all) to 21:1 (pure black on pure white, the maximum possible). WCAG level AA requires a contrast ratio of at least 4.5:1 for normal body text, and a slightly relaxed 3:1 for large text (18pt and above, or 14pt bold and above).

Let's actually compute it for four candidate greys on a plain white background (white has L = 1 exactly). Take #707070 first: the channel value is 112 out of 255, so s = 112/255 = 0.439. Since that is above 0.03928, we linearise: ((0.439 + 0.055)/1.055)^2.4 = (0.468)^2.4 ≈ 0.162. The contrast ratio against white is (1 + 0.05)/(0.162 + 0.05) = 1.05/0.212 ≈ 4.95:1. That comfortably clears the 4.5:1 bar.

Now walk the same two steps for three more greys and place them in a table:

ColourDecimal (0–255)Luminance LContrast vs. whiteWCAG AA (4.5:1)?
#707070112≈0.162≈4.95:1✅ Pass, comfortably
#767676118≈0.181≈4.54:1✅ Pass, right at the edge
#777777119≈0.185≈4.48:1❌ Fail, just below
#808080128≈0.216≈3.95:1❌ Fail

Notice the direction of the pattern: as the decimal channel value climbs from 112 to 128, the grey gets lighter (closer to white), its luminance climbs (closer to white's luminance of 1), and the contrast against a white background falls. This makes sense once you see the formula — contrast measures the gap between two luminance values, and a lighter grey has a smaller gap from white than a darker grey does. So #767676 is not the darkest grey that "barely" passes; it is close to the lightest grey that still passes 4.5:1 on a white background — one hex step lighter, at #777777, and it fails. Any grey darker than #767676 (like #707070 or pure black) passes with more room to spare; any grey lighter than it fails.

Common misconception: many students assume "light grey text looks softer and friendlier, so it's a fine, safe design choice, as long as it isn't literally white-on-white." The maths above shows this is false — the readable range shrinks fast, and a grey that still looks perfectly legible on a laptop in a dim room can drop below the legal accessibility threshold, becoming genuinely hard to read on a phone screen in bright outdoor light, which is an extremely common way students in India actually use the internet. Contrast is not only a "blind users" feature; it affects anyone with low vision, ageing eyes, an older or dimmer screen, or simply bad lighting.

Four grey text swatches on white, showing contrast ratio falling as the grey lightens, with the WCAG AA 4.5:1 pass/fail line between #767676 and #777777 Text colour vs. white background: contrast ratio Lighter grey (higher decimal) = smaller luminance gap from white = lower contrast Aa #707070 4.95 : 1 PASS Aa #767676 4.54 : 1 PASS (edge) Aa #777777 4.48 : 1 FAIL Aa #808080 3.95 : 1 FAIL WCAG AA 4.5:1 threshold sits between #767676 and #777777

Alt Text: Describing Images to Someone Who Cannot See Them

Every <img> tag should carry an alt attribute, but writing a good one is a skill, not a formality. Consider a school website posting a photo from a Republic Day event:

<!-- Weak: redundant and unhelpful -->
<img src="event1.jpg" alt="image">

<!-- Still weak: states the obvious, wastes the listener's time -->
<img src="event1.jpg" alt="picture of an image showing students">

<!-- Good: describes what matters in context -->
<img src="event1.jpg" alt="Students in white uniforms saluting the Indian flag during the Republic Day assembly">

Two rules make the difference. First, never begin alt text with "image of" or "picture of" — a screen reader already announces "image" itself before reading the alt text, so writing it again just wastes the listener's time on every single photo on the page. Second, describe the image's content and purpose, not its file properties: not "a JPEG photograph," but what a sighted person would actually take away from glancing at it.

There is one more case students often get backwards: purely decorative images, such as a thin divider line or a repeated background flourish, that carry no information. For these, the correct fix is not to omit the alt attribute — it is to set it to an explicitly empty string, alt="". An empty alt tells the screen reader "skip this, it's decorative, don't announce anything." If you omit alt entirely, many screen readers fall back to reading out the raw image filename, so a missing divider image might get announced as "line, dash, underscore, two, dot, P, N, G" — genuinely worse than saying nothing.

<img src="divider-line.png" alt="">  <!-- correct: silently skipped -->
<img src="divider-line.png">        <!-- wrong: may read the filename aloud -->

Keyboard Navigation: The Web Without a Mouse

Many users cannot operate a mouse or trackpad precisely — because of a motor disability, a temporary injury, or simply because they prefer typing. Every interactive control on your page must be reachable by pressing Tab and activatable by Enter or Spacebar, with no exceptions. As you saw earlier, native elements like <button>, <a href>, and form inputs get this automatically. If you truly must build a custom interactive widget out of a non-interactive element, you have to add three things by hand: tabindex="0" to make it reachable by Tab, an ARIA role to describe what it is, and a keyboard event handler, because clicking it with a mouse does not automatically make Enter or Space work.

<div role="button" tabindex="0"
     onclick="addToCart()"
     onkeydown="if(event.key==='Enter'||event.key===' '){addToCart();}">
  Add to Cart
</div>

This works, but compare it honestly to the one-word fix from earlier: swapping the tag to <button>. The div version needed three extra attributes and a hand-written keyboard handler just to catch up to what <button> gives away for free. This is why experienced developers treat "build a custom div-based widget" as a last resort, only for genuinely novel interface patterns a native tag cannot express.

There is a second, equally common keyboard bug: the focus outline. When you press Tab, the browser draws a visible highlight (usually a blue ring) around whichever element is currently focused, so a keyboard user always knows where they are on the page. Many students, chasing a cleaner visual design, write this rule:

*:focus {
  outline: none;
}

Common misconception: "removing the outline just makes the design tidier, since I have hover states for feedback." Hover states only fire on mouse-over — a keyboard user never hovers anything. Deleting the focus outline without replacing it does not clean up the design; it makes the entire page unusable for keyboard-only users, because they lose all visual indication of where they currently are while tabbing through the page, effectively navigating blind. The correct fix is to restyle the outline, never delete it:

*:focus-visible {
  outline: 3px solid #1a73e8;
  outline-offset: 2px;
}

This keeps a clear, deliberately designed focus indicator while letting you control its exact look.

ARIA: The Reinforcement, Not the Foundation

ARIA (Accessible Rich Internet Applications) is a set of extra HTML attributes — role, aria-label, aria-live, aria-expanded, and others — that let you describe roles and states the browser cannot infer on its own. The single most important rule governing ARIA, sometimes literally called the "First Rule of ARIA," is: if a native HTML element already has the role and behaviour you need, use it instead of adding ARIA to a generic one. ARIA is a patch for gaps native HTML cannot fill, not a replacement for choosing the right tag.

One place ARIA genuinely earns its place is announcing content that changes without a page reload — something native HTML has no built-in way to express. Picture a UPI-style payment confirmation screen where the status text updates live via JavaScript after the user taps "Pay":

<p id="payStatus" role="status" aria-live="polite">
  Processing payment…
</p>

<script>
  function onPaymentConfirmed() {
    document.getElementById("payStatus").textContent = "Payment successful";
  }
</script>

Without aria-live="polite", a screen reader has no idea the text inside that paragraph just changed, and a blind user would have to manually re-navigate to that exact spot to discover their payment went through. With it, the moment the text updates, the screen reader automatically speaks the new content — "Payment successful" — without the user lifting a finger. Common misconception: students sometimes assume sprinkling ARIA attributes onto every element makes a page "more accessible" by default. In fact, an incorrect or contradictory ARIA role (say, role="button" placed on an element that also has a native <a href>, creating two conflicting roles) can confuse a screen reader worse than having no ARIA at all, because the browser now has to guess which description to trust. ARIA is precise reinforcement for specific gaps, applied deliberately — not a blanket "accessibility mode" you switch on.

Where This Fits Your CBSE Work

If you are building web pages for your Class 9 Information Technology (Code 402) or Artificial Intelligence (Code 417) practicals, these are not abstract rules — they are testable habits: does every clickable element use <button> or <a>, does every <img> carry meaningful alt text, does your CSS ever delete a focus outline without replacing it, and would your body-text colour survive the contrast arithmetic you just worked through by hand? These same ideas resurface, formalised further, if you take Informatics Practices in Class 11–12.

Check Your Understanding

  1. A classmate writes <span onclick="openMenu()">Menu</span> for a navigation menu button. Name two concrete ways a screen-reader user and a keyboard-only user would each be affected, and give the one-line fix.
  2. Compute, showing your normalise-then-linearise steps, whether #666666 (decimal 102) passes WCAG AA 4.5:1 for normal text on a white background. (Hint: it is darker than #707070, so predict the direction of your answer before you calculate.)
  3. A decorative background swirl image is missing its alt attribute entirely. Explain, in terms of what a screen reader actually does, why this is worse than adding alt="".
  4. Explain why *:focus { outline: none; } with no replacement style is a genuine accessibility failure and not just a stylistic choice, referencing what a keyboard-only user experiences.
  5. A developer adds role="button" and aria-label="Submit" to a <div>, but forgets tabindex="0" and the keyboard handler. List everything still missing compared to simply using <button>Submit</button>.

Summary

Accessibility means every visitor — including screen-reader users, keyboard-only users, colour-blind users, and people browsing in bad lighting — can perceive, understand, and operate your app; WCAG's POUR principles (Perceivable, Operable, Understandable, Robust) at conformance level AA are the practical, industry-standard target. Semantic HTML matters because the browser silently builds a second, parallel accessibility tree from your tags, and native elements like <button> and <a> earn correct role, name, and full keyboard support automatically, while generic <div>/<span> elements earn none of it and must be rebuilt by hand with ARIA and JavaScript. Colour contrast is governed by an exact, computable formula — normalise the channel to 0–1, gamma-correct it into a luminance, then take (L1+0.05)/(L2+0.05) — and the direction of the relationship is: lighter grey text always means lower contrast against a white background, with #767676 sitting essentially at the WCAG AA 4.5:1 boundary and anything lighter failing. Alt text should describe content and purpose without redundant phrases like "image of," and decorative images need an explicit empty alt="", never a missing attribute. Every interactive control must work by keyboard alone, and focus outlines must be restyled, never deleted. Finally, ARIA fills gaps native HTML cannot express — like announcing live-updating content — but should never replace choosing the correct native element first.

← Web Workers: Multi-threading in JavaScriptInternationalization: Supporting Multiple Languages →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn