{"id":"l-a11y-1-2","courseId":"web-accessibility","lang":"en","kind":"lesson","summary":"Explain how screen readers, magnifiers, switch access and voice control operate a page, and read the accessibility tree the browser builds for them.","html":"<p>Open Ticketly with your screen turned off. That sounds like a stunt, but it is what Samir does every working day, and it is the fastest way to understand what your code really sends to the people using it. Before you fix anything, you need a clear picture of how assistive technology (AT) gets information out of a web page.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The accessibility tree</h2>\n<p>Assistive technologies don’t look at your pixels. The browser takes the DOM, combines it with CSS, and builds a second, simpler tree: the <strong>accessibility tree</strong>. Each node in it has a few properties:</p>\n<ul>\n<li>a <strong>role</strong>: button, link, heading (level 2), textbox, checkbox, list, generic;</li>\n<li>a <strong>name</strong>: the text that identifies it, such as “Book now”;</li>\n<li><strong>states and values</strong>: checked, expanded, disabled, the current text in an input.</li>\n</ul>\n<p>The browser hands that tree to the operating system’s accessibility API, and the screen reader, magnifier or voice control software reads it from there.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 220\" role=\"img\" aria-labelledby=\"l-a11y-1-2-t1\"><defs><marker id=\"arrow-l-a11y-1-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-a11y-1-2-t1\">The DOM and CSS feed the browser, which builds the accessibility tree; the tree is exposed through the operating system accessibility API to screen readers, magnifiers, switch and voice control software.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"70\" width=\"130\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"75\" y=\"102\" text-anchor=\"middle\">DOM + CSS</text>\n  <text class=\"d-label-muted\" x=\"75\" y=\"124\" text-anchor=\"middle\">your code</text>\n  <path class=\"d-arrow\" d=\"M140 105 L190 105\" marker-end=\"url(#arrow-l-a11y-1-2-1)\"/>\n  <rect class=\"d-box-primary\" x=\"195\" y=\"60\" width=\"160\" height=\"90\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"275\" y=\"95\" text-anchor=\"middle\">Accessibility</text>\n  <text class=\"d-label-strong\" x=\"275\" y=\"115\" text-anchor=\"middle\">tree</text>\n  <text class=\"d-label-muted\" x=\"275\" y=\"137\" text-anchor=\"middle\">role, name, state</text>\n  <path class=\"d-arrow\" d=\"M355 105 L405 105\" marker-end=\"url(#arrow-l-a11y-1-2-1)\"/>\n  <rect class=\"d-box\" x=\"410\" y=\"70\" width=\"120\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"470\" y=\"102\" text-anchor=\"middle\">OS API</text>\n  <text class=\"d-label-muted\" x=\"470\" y=\"124\" text-anchor=\"middle\">UIA, AX, AT-SPI</text>\n  <path class=\"d-arrow\" d=\"M530 105 L570 40\" marker-end=\"url(#arrow-l-a11y-1-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M530 105 L570 105\" marker-end=\"url(#arrow-l-a11y-1-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M530 105 L570 170\" marker-end=\"url(#arrow-l-a11y-1-2-1)\"/>\n  <text class=\"d-label\" x=\"578\" y=\"45\">Screen reader</text>\n  <text class=\"d-label\" x=\"578\" y=\"110\">Magnifier</text>\n  <text class=\"d-label\" x=\"578\" y=\"175\">Voice, switch</text>\n</svg><figcaption>From your markup to the user’s ears</figcaption></figure>\n<p>Here is part of Ticketly’s event card, and what the tree contains for it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">ticketly/index.html</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Harbour Lights: Friday&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"btn\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onclick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">book</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">('fri')\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Book now&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onclick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">book</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">('sat')\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Book now&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>heading &quot;Harbour Lights: Friday&quot; (level 3)\ngeneric\n  text &quot;Book now&quot;\nbutton &quot;Book now&quot;</code></pre></div>\n<p>The div shows the same text, but its role is generic. A screen reader reads “Book now” as a sentence, with no hint that it does anything, and it can’t be reached with the Tab key. You can inspect this yourself: in Chrome or Edge DevTools, select an element and open the <strong>Accessibility</strong> pane; Firefox has a dedicated Accessibility Inspector tab.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Four ways people operate Ticketly</h2>\n<p><strong>Screen readers</strong> (NVDA and JAWS on Windows, VoiceOver on Apple devices, TalkBack on Android) turn the tree into speech or braille. Users rarely listen to a page top to bottom. In WebAIM’s 2026 survey, 67.8% of screen reader users said their first move on a long page is to jump between headings. They also pull up lists of links, landmarks and form fields. Your structure is their table of contents.</p>\n<p><strong>Screen magnifiers</strong> (Windows Magnifier, macOS Zoom, ZoomText) enlarge a small region that follows the focus or pointer. Maha, a designer on my team, works at 400%: she sees about a sixteenth of the screen at once. Anything that happens far from her focus, like a toast in the opposite corner, didn’t happen as far as she is concerned.</p>\n<p><strong>Switch access</strong> users press one or two physical switches. The software steps through focusable elements, and a switch press activates the current one. If an element isn’t focusable, it can’t be reached at all. Switch users depend on the same keyboard operability you will build in Section 3.</p>\n<p><strong>Voice control</strong> (Voice Control on macOS and iOS, Dragon on Windows) lets people say “click Book now” or “show numbers”. The software matches spoken words against accessible names. When a button reads “Book seats” on screen but has <code dir=\"ltr\">aria-label=&quot;cta-primary&quot;</code>, the words Dina says don’t match the name and nothing happens. WCAG calls this <strong>Label in Name</strong>: the accessible name must contain the visible text.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Hiding content the wrong way</span></p>\n<p><code dir=\"ltr\">display: none</code>, <code dir=\"ltr\">visibility: hidden</code> and the <code dir=\"ltr\">hidden</code> attribute remove content from the accessibility tree as well as the screen. <code dir=\"ltr\">aria-hidden=&quot;true&quot;</code> removes it from the tree only. People reach for these interchangeably and either hide meaningful text from screen readers or announce clutter. Decide first who should perceive the content, then pick the tool.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Showing it to the tree, not the eye</h2>\n<p>Two patterns cover most cases on Ticketly.</p>\n<p>When a visual cue carries meaning that has no text, add text that only the tree sees. Ticketly marks a sold-out date with a strike-through and grey colour. A screen reader announces “SAR 180” and Samir happily tries to buy it. Add a visually-hidden note:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">ticketly/styles.css</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.visually-hidden</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  position</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">absolute</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  width</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  height</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  margin</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">-1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  overflow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">hidden</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  clip-path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">inset</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">50</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  white-space</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">nowrap</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  border</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"price sold\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>SAR 180 &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"visually-hidden\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(sold out)&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>When an element is decorative, hide it from the tree. Ticketly puts a small calendar icon before every date. Inline SVGs can be announced as “image” or “graphic”, which is noise. <code dir=\"ltr\">aria-hidden=&quot;true&quot;</code> removes them:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">svg</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"icon\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-hidden</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"true\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> viewBox</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"0 0 16 16\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>…&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">svg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> 12 June, 8 pm&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Visible beats hidden</span></p>\n<p>A visually-hidden note is right when the visual design already communicates the meaning. If sighted users could also miss the strike-through, write “Sold out” in plain view instead. Text everyone can see helps more people than text only some can hear.</p>\n</aside>\n<p>The tree is the contract between your code and every AT. Next you will see the standard that defines what that contract must include: WCAG 2.2.</p>\n","toc":[{"id":"sec-1","text":"The accessibility tree","level":2},{"id":"sec-2","text":"Four ways people operate Ticketly","level":2},{"id":"sec-3","text":"Showing it to the tree, not the eye","level":2}],"takeaways":["Assistive technologies don’t read your pixels; they read the accessibility tree, where each node has a role, a name, and states or a value.","Screen reader users move by headings, landmarks, links and form fields, so structure is their table of contents.","Magnifier, switch and voice control users depend on visible focus, keyboard operability and visible labels that match accessible names.","display none, the hidden attribute and aria-hidden remove content from the tree; a visually-hidden class hides it from the eye but keeps it for screen readers."],"further":[{"title":"MDN: Accessibility tree","url":"https://developer.mozilla.org/en-US/docs/Glossary/Accessibility_tree"},{"title":"W3C WAI: Tools and Techniques used by people with disabilities","url":"https://www.w3.org/WAI/people-use-web/tools-techniques/"},{"title":"MDN: aria-hidden","url":"https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-hidden"}],"quiz":[{"q":"<p>A screen reader user reaches this element. What does the accessibility tree give the screen reader to announce?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"btn\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onclick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">book</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">()\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Book now&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n","options":[{"text":"Role “button”, name “Book now”","why":"The browser doesn’t infer a button role from a class name or a click handler. Only a real button element (or an explicit role) produces that."},{"text":"Plain text “Book now” with no role and no way to activate it","why":"Correct. A div has a generic role, so the screen reader reads the text as static content and the user has no hint that it does anything."},{"text":"Nothing at all, because divs are not in the accessibility tree","why":"Divs with text do appear, as generic containers whose text is readable. The problem is the missing role, not a missing node."}],"answer":1},{"q":"<p>Maha uses screen magnification at 400%, so she sees about a sixteenth of the page at a time. Ticketly shows “Added to basket” in a toast at the top-right corner while she is clicking a button bottom-left. What is the main barrier?</p>","options":[{"text":"The toast has no ARIA role","why":"A role helps screen reader users, but Maha reads with her eyes. A perfectly marked-up toast is still invisible to her if it appears far outside her viewport."},{"text":"The toast text is too short","why":"Length is not the issue here. The message appears in a part of the screen she is not looking at."},{"text":"The message appears far away from where her attention and focus are","why":"Correct. Magnifier users see a small window around the focus or pointer, so feedback should appear near the control that caused it."}],"answer":2},{"q":"<p>Dina controls her computer by voice. The button shows the text “Book seats” but has <code dir=\"ltr\">aria-label=&quot;cta-primary&quot;</code>. What happens when she says “click Book seats”?</p>","options":[{"text":"It fails, because the accessible name “cta-primary” doesn’t contain the words she can see","why":"Correct. This breaks WCAG 2.5.3 Label in Name. Remove the aria-label so the name comes from the visible text."},{"text":"It works, because voice control reads the visible text","why":"Voice control software matches spoken words against the accessible name, and aria-label replaces the visible text as the name."},{"text":"It works only if the button also has a title attribute","why":"A title is ignored when aria-label is present, and it wouldn’t help a name that doesn’t match the visible words."}],"answer":0},{"q":"<p>Which CSS hides a “Sold out” note from sighted users but keeps it in the accessibility tree?</p>","options":[{"text":"<code dir=\"ltr\">display: none</code>","why":"display none removes the element from the accessibility tree as well as from the screen, so screen readers never hear it."},{"text":"<code dir=\"ltr\">visibility: hidden</code>","why":"visibility hidden also removes the content from the accessibility tree."},{"text":"<code dir=\"ltr\">opacity: 0</code> on the parent card","why":"That hides the whole card from sighted users, which is the opposite of what you want, and it doesn’t help screen reader users either."},{"text":"A visually-hidden class that clips the element to a 1px box with <code dir=\"ltr\">position: absolute</code>","why":"Correct. The element still renders, so it stays in the tree, but it takes no visible space."}],"answer":3}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<h2>Harbour Lights dates</h2>\n<article class=\"event\">\n  <h3>Friday</h3>\n  <p class=\"date\"><svg class=\"icon\" viewBox=\"0 0 16 16\"><rect x=\"1\" y=\"3\" width=\"14\" height=\"12\" rx=\"2\"/></svg> 12 June, 8 pm</p>\n  <p class=\"price sold\" id=\"fri-price\"><svg class=\"icon\" viewBox=\"0 0 16 16\"><circle cx=\"8\" cy=\"8\" r=\"7\"/></svg> SAR 180</p>\n</article>\n<article class=\"event\">\n  <h3>Saturday</h3>\n  <p class=\"date\"><svg class=\"icon\" viewBox=\"0 0 16 16\"><rect x=\"1\" y=\"3\" width=\"14\" height=\"12\" rx=\"2\"/></svg> 13 June, 8 pm</p>\n  <p class=\"price\" id=\"sat-price\"><svg class=\"icon\" viewBox=\"0 0 16 16\"><circle cx=\"8\" cy=\"8\" r=\"7\"/></svg> SAR 180</p>\n</article>","css":".event { border: 1px solid #c9c4dc; border-radius: 12px; padding: 12px 16px; margin-block-end: 12px; }\n.event h3 { margin: 0 0 4px; }\n.event p { margin: 4px 0; display: flex; align-items: center; gap: 6px; }\n.icon { width: 16px; height: 16px; fill: #5b5670; }\n.sold { text-decoration: line-through; color: #6b6680; }\n\n/* Add a .visually-hidden class here if you need one */\n","js":""},"solution":{"html":"<h2>Harbour Lights dates</h2>\n<article class=\"event\">\n  <h3>Friday</h3>\n  <p class=\"date\"><svg class=\"icon\" aria-hidden=\"true\" viewBox=\"0 0 16 16\"><rect x=\"1\" y=\"3\" width=\"14\" height=\"12\" rx=\"2\"/></svg> 12 June, 8 pm</p>\n  <p class=\"price sold\" id=\"fri-price\"><svg class=\"icon\" aria-hidden=\"true\" viewBox=\"0 0 16 16\"><circle cx=\"8\" cy=\"8\" r=\"7\"/></svg> SAR 180 <span class=\"visually-hidden\">(sold out)</span></p>\n</article>\n<article class=\"event\">\n  <h3>Saturday</h3>\n  <p class=\"date\"><svg class=\"icon\" aria-hidden=\"true\" viewBox=\"0 0 16 16\"><rect x=\"1\" y=\"3\" width=\"14\" height=\"12\" rx=\"2\"/></svg> 13 June, 8 pm</p>\n  <p class=\"price\" id=\"sat-price\"><svg class=\"icon\" aria-hidden=\"true\" viewBox=\"0 0 16 16\"><circle cx=\"8\" cy=\"8\" r=\"7\"/></svg> SAR 180</p>\n</article>","css":".event { border: 1px solid #c9c4dc; border-radius: 12px; padding: 12px 16px; margin-block-end: 12px; }\n.event h3 { margin: 0 0 4px; }\n.event p { margin: 4px 0; display: flex; align-items: center; gap: 6px; }\n.icon { width: 16px; height: 16px; fill: #5b5670; }\n.sold { text-decoration: line-through; color: #6b6680; }\n\n.visually-hidden {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  margin: -1px;\n  padding: 0;\n  overflow: hidden;\n  clip-path: inset(50%);\n  white-space: nowrap;\n  border: 0;\n}\n","js":""},"checks":[{"id":"sold-out-text","test":"return /sold out/i.test($('#fri-price').textContent);"},{"id":"sold-out-in-tree","test":"const p = $('#fri-price'); const walker = document.createTreeWalker(p, NodeFilter.SHOW_TEXT); let n; while ((n = walker.nextNode())) { if (!/sold out/i.test(n.textContent)) continue; let el = n.parentElement; let ok = true; while (el) { const cs = getComputedStyle(el); if (cs.display === 'none' || cs.visibility === 'hidden' || el.getAttribute('aria-hidden') === 'true' || el.hidden) ok = false; if (el === p) break; el = el.parentElement; } if (ok && !p.closest('[aria-hidden=\"true\"]')) return true; } return false;"},{"id":"icons-hidden","test":"const icons = $$('svg'); return icons.length === 4 && icons.every((s) => s.closest('[aria-hidden=\"true\"]'));"},{"id":"saturday-available","test":"return !/sold out/i.test($('#sat-price').textContent) && /SAR 180/.test($('#sat-price').textContent);"}],"title":"Tell the tree what the eye sees","prompt":"<p>Ticketly’s Friday price (<code dir=\"ltr\">#fri-price</code>) is struck through and greyed out because the date is sold out, but a screen reader only hears “SAR 180”. Meanwhile every decorative SVG icon can be announced as an unlabeled graphic.</p>\n<ol>\n<li>Add the words <strong>sold out</strong> to <code dir=\"ltr\">#fri-price</code> so they reach the accessibility tree. You can show them, or hide them visually with a <code dir=\"ltr\">.visually-hidden</code> class (not <code dir=\"ltr\">display: none</code>).</li>\n<li>Hide all four decorative <code dir=\"ltr\">&lt;svg&gt;</code> icons from assistive technology.</li>\n<li>Leave Saturday (<code dir=\"ltr\">#sat-price</code>) available.</li>\n</ol>\n","hints":["Text that is display: none or visibility: hidden is removed from the accessibility tree too, so it won’t be announced.","A visually-hidden class clips the element to 1px with position: absolute, overflow: hidden and clip-path: inset(50%).","Decorative icons get aria-hidden=“true” on the <svg> element itself."],"explanation":"<p>The strike-through is purely visual, so the meaning needs text. A <code dir=\"ltr\">&lt;span class=&quot;visually-hidden&quot;&gt;(sold out)&lt;/span&gt;</code> keeps the words in the accessibility tree while the design stays as it was; plain visible text would be just as valid, and arguably better for sighted users who miss the strike-through. The icons add nothing the text doesn’t already say, so <code dir=\"ltr\">aria-hidden=&quot;true&quot;</code> removes them from the tree and keeps the announcement short: “Friday, heading level 3. 12 June, 8 pm. SAR 180 (sold out)”.</p>\n","checkLabels":{"sold-out-text":"The Friday price contains the words “sold out”","sold-out-in-tree":"The “sold out” text is not hidden from assistive technology","icons-hidden":"All four decorative icons are hidden with aria-hidden=“true”","saturday-available":"Saturday still shows SAR 180 and is not marked sold out"}},"words":719,"readMinutes":3}