{"id":"l-a11y-5-2","courseId":"web-accessibility","lang":"en","kind":"lesson","summary":"Test Ticketly by hand with a keyboard, NVDA and VoiceOver using the core navigation commands, recognise what correct announcements sound like, and write findings developers can act on.","html":"<p>Automated tools told you what Ticketly is missing. A keyboard and a screen reader tell you what it is like to use. This is the part of testing that most teams skip, and it is the part that finds the problems people actually complain about. You don’t need to become an expert screen reader user. You need about fifteen commands and a habit of listening carefully.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The keyboard pass</h2>\n<p>Start every manual test without a mouse. Go from the address bar to the end of the page with Tab, then back with Shift+Tab, and at each stop ask:</p>\n<ol>\n<li><strong>Can I reach it?</strong> Every control a mouse can use must be reachable.</li>\n<li><strong>Can I see it?</strong> Focus is clearly visible and not hidden behind sticky bars.</li>\n<li><strong>Is the order logical?</strong> Focus follows the reading order of the page.</li>\n<li><strong>Does it work?</strong> Enter follows links and activates buttons; Space activates buttons and toggles checkboxes; arrow keys work inside radios, tabs and menus.</li>\n<li><strong>Can I get out?</strong> Escape closes dialogs and menus, focus returns to the opener, and nothing traps focus.</li>\n</ol>\n<p>On Ticketly, this pass alone found the positive tabindex, the invisible focus, the div button and the modal that left focus behind, before anyone opened a screen reader.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Which screen readers to test</h2>\n<p>In WebAIM’s 2026 survey, the most common primary desktop screen readers were <strong>JAWS</strong> (55.0%) and <strong>NVDA</strong> (32.9%), both on Windows, followed by <strong>VoiceOver</strong> on macOS (6.5%). On mobile, 72.2% of respondents use <strong>VoiceOver</strong> on iOS, well ahead of <strong>TalkBack</strong> on Android (29.5%). A practical test matrix:</p>\n<ul>\n<li><strong>NVDA with Chrome or Firefox</strong> on Windows. NVDA is free and open source, and behaves closely enough to JAWS to catch most problems.</li>\n<li><strong>VoiceOver with Safari</strong> on macOS, built in.</li>\n<li><strong>VoiceOver on iOS</strong> for anything people use on phones, which at a festival is almost everything.</li>\n</ul>\n<h2 id=\"sec-3\" tabindex=\"-1\">NVDA essentials</h2>\n<p>NVDA has two modes. In <strong>browse mode</strong>, NVDA reads the page with a virtual cursor and single letters jump between elements. In <strong>focus mode</strong>, keys go to the control, so you can type in a field. NVDA switches automatically when you enter a field, and NVDA+Space switches by hand. The <strong>NVDA key</strong> is Insert by default; you can also enable Caps Lock as the NVDA key, which is handy on laptops.</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Action</th>\n<th>NVDA keys</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Start NVDA / stop speech</td>\n<td>Ctrl+Alt+N / Ctrl</td>\n</tr>\n<tr>\n<td>Read from here</td>\n<td>NVDA+Down Arrow</td>\n</tr>\n<tr>\n<td>Next line or item</td>\n<td>Down Arrow</td>\n</tr>\n<tr>\n<td>Next heading / heading level 2</td>\n<td>H / 2</td>\n</tr>\n<tr>\n<td>Next landmark</td>\n<td>D</td>\n</tr>\n<tr>\n<td>Next form field / button / link</td>\n<td>F / B / K</td>\n</tr>\n<tr>\n<td>Next table, then move through cells</td>\n<td>T, then Ctrl+Alt+Arrows</td>\n</tr>\n<tr>\n<td>List of headings, links, landmarks</td>\n<td>NVDA+F7</td>\n</tr>\n</tbody>\n</table></div><p>Add Shift to any single-letter command to go backwards.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">VoiceOver essentials (macOS)</h2>\n<p>Turn VoiceOver on and off with <strong>Cmd+F5</strong>. The <strong>VO keys</strong> are Control+Option. Before you start, open Safari’s settings and enable <strong>“Press Tab to highlight each item on a webpage”</strong>; otherwise Tab skips links in Safari and you will report false bugs.</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Action</th>\n<th>VoiceOver keys</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Next / previous item</td>\n<td>VO+Right / VO+Left</td>\n</tr>\n<tr>\n<td>Activate</td>\n<td>VO+Space</td>\n</tr>\n<tr>\n<td>Read from here / stop speech</td>\n<td>VO+A / Control</td>\n</tr>\n<tr>\n<td>Next heading</td>\n<td>VO+Cmd+H</td>\n</tr>\n<tr>\n<td>Rotor (headings, links, landmarks, form controls)</td>\n<td>VO+U, then Left/Right to switch lists</td>\n</tr>\n</tbody>\n</table></div><p>On iOS, swipe right and left to move between items, double-tap to activate, and rotate two fingers on the screen to open the rotor.</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>Testing only by reading top to bottom</span></p>\n<p>New testers press “read all” and listen to the whole page. Real users almost never do that. Test the way they navigate: list the headings, jump by landmark, tab through the form, open the rotor’s links list. That is where missing headings, duplicate “Book now” names and unlabelled landmarks become obvious.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">What correct sounds like</h2>\n<p>Every interactive element should be announced with a <strong>name</strong>, a <strong>role</strong> and, where relevant, a <strong>state</strong> or value. Listen for the missing piece:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>You hear</th>\n<th>Verdict</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>“Book now Friday pass, button”</td>\n<td>Name and role: good</td>\n</tr>\n<tr>\n<td>“Save to wishlist, toggle button, not pressed”</td>\n<td>Name, role, state: good</td>\n</tr>\n<tr>\n<td>“edit text, blank”</td>\n<td>No name: the field has no label</td>\n</tr>\n<tr>\n<td>“Pay now” (and Tab skips it)</td>\n<td>No role, not focusable: a clickable div</td>\n</tr>\n<tr>\n<td>“graphic, IMG underscore 2041 dot jpeg”</td>\n<td>Missing alt attribute</td>\n</tr>\n<tr>\n<td>“How do refunds work?, button” on a disclosure</td>\n<td>No state: aria-expanded is missing</td>\n</tr>\n</tbody>\n</table></div><p>Also listen for what doesn’t happen: no announcement after adding a ticket, nothing said when a dialog opens, an error that appears silently.</p>\n<h2 id=\"sec-6\" tabindex=\"-1\">Writing it up</h2>\n<p>A finding a developer can fix in one go has six parts:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">findings/ticketly-042.txt</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>Title:    Quantity stepper buttons have no accessible name\nWhere:    /events/friday, Friday pass card\nSteps:    NVDA + Chrome. Tab to the &quot;−&quot; button after &quot;Quantity&quot;.\nActual:   NVDA says &quot;button&quot;. Same for &quot;+&quot;.\nExpected: &quot;Fewer Friday passes, button&quot; and &quot;More Friday passes, button&quot;.\nWCAG:     4.1.2 Name, Role, Value (A). Severity: high (blocks purchase).</code></pre></div>\n<aside class=\"callout callout-why\" 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\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Why this matters</span></p>\n<p>Testing with assistive technology yourself is not the same as testing with disabled people. Samir uses NVDA at a speed and with habits you won’t reproduce in an afternoon. Your own testing catches the obvious barriers cheaply; paid usability sessions with disabled participants find the rest, and they are worth budgeting for at least once a year.</p>\n</aside>\n<p>You now have every tool you need: automated scans, a keyboard and two screen readers. The final lesson puts them together into a complete audit of Ticketly, and into habits that keep it accessible after you leave.</p>\n","toc":[{"id":"sec-1","text":"The keyboard pass","level":2},{"id":"sec-2","text":"Which screen readers to test","level":2},{"id":"sec-3","text":"NVDA essentials","level":2},{"id":"sec-4","text":"VoiceOver essentials (macOS)","level":2},{"id":"sec-5","text":"What correct sounds like","level":2},{"id":"sec-6","text":"Writing it up","level":2}],"takeaways":["A keyboard pass checks reachability, order, visible focus, operation with Enter and Space, Escape on overlays, and no traps.","Test NVDA with Chrome or Firefox on Windows and VoiceOver with Safari on macOS and iOS; together they cover most real screen reader use.","Screen reader users navigate by headings, landmarks, links and form fields, so test with those commands, not only by reading from the top.","Every announcement should give a name, a role and any state; a missing piece of that trio is a bug."],"further":[{"title":"WebAIM: Using NVDA to Evaluate Web Accessibility","url":"https://webaim.org/articles/nvda/"},{"title":"WebAIM: Using VoiceOver to Evaluate Web Accessibility","url":"https://webaim.org/articles/voiceover/"},{"title":"WebAIM: Keyboard Accessibility","url":"https://webaim.org/techniques/keyboard/"},{"title":"W3C WAI: Easy Checks, A First Review of Web Accessibility","url":"https://www.w3.org/WAI/test-evaluate/preliminary/"}],"quiz":[{"q":"<p>You are testing Ticketly with NVDA in browse mode. Which key jumps to the next heading?</p>","options":[{"text":"H","why":"Correct. In browse mode single letters navigate by element type. Shift+H goes back, and 1 to 6 jump to a heading of that level."},{"text":"Tab","why":"Tab moves between focusable elements such as links and buttons, and skips headings entirely."},{"text":"NVDA+F7","why":"That opens the Elements List, a dialog listing headings, links, landmarks and more. Useful, but it isn’t the single-key jump."},{"text":"D","why":"D jumps to the next landmark, such as main or navigation, not the next heading."}],"answer":0},{"q":"<p>On macOS Safari with VoiceOver on, Tab skips every link on Ticketly and only visits form fields. What is going on?</p>","options":[{"text":"The links are missing href attributes","why":"That is possible on a broken page, but here every link works in other browsers. This is a Safari setting, not a page bug."},{"text":"VoiceOver can’t read links in Safari","why":"VoiceOver reads links in Safari fine with VO+arrow keys and the rotor. The issue is only the Tab key’s behaviour."},{"text":"Safari by default doesn’t include links in the Tab order; enable “Press Tab to highlight each item” or use Option+Tab","why":"Correct. Turn the setting on before keyboard testing in Safari, or you will report false bugs."}],"answer":2},{"q":"<p>Focus reaches Ticketly’s “Show more dates” control and NVDA says “Show more dates, button”. The control reveals a hidden list. What is missing?</p>","options":[{"text":"Nothing; the name and role are both present","why":"For a toggle that shows and hides content, the state is the third part users need. Without it they can’t tell whether the list is open."},{"text":"The state, “collapsed” or “expanded”, which comes from aria-expanded","why":"Correct. Name, role and state together tell the user what the control is, what it does and where it stands."},{"text":"A description from aria-describedby explaining that it is a button","why":"The role already says it is a button. A description would repeat that while the state is still missing."},{"text":"The word “link”, because it reveals content","why":"Revealing content in place is an action, so button is the right role. The missing piece is the state."}],"answer":1}],"exercise":{"kind":"choice","multiple":true,"options":["div-pay","radio-group","blank-field","dialog-open","file-name","status-update","no-state"],"correct":["div-pay","blank-field","file-name","no-state"],"title":"Listen to Ticketly's checkout","prompt":"<p>You tested Ticketly’s checkout with NVDA and Chrome and wrote down what you heard at seven points. Select <strong>every announcement that reveals a bug</strong>. For each one, think about which of name, role and state is missing or wrong.</p>\n","hints":["A good announcement has a name, a role, and a state when the control has one.","Listen for what is missing: no name, no role, no state, or a file name where a description should be.","Four of the seven are bugs."],"explanation":"<p>Four announcements reveal bugs. “Pay now” with no role, skipped by Tab, is a clickable div: use a button. “edit text, blank” is a field with no label. “graphic, IMG underscore 2041 dot jpeg” means the image has no alt attribute, so the screen reader falls back to the file name. And “How do refunds work?, button” on a show/hide control is missing its state, so aria-expanded needs adding. The other three are what correct sounds like: a named radio group with position, a named dialog that announces itself and its first field, and a polite status message read without moving focus.</p>\n","checkLabels":{},"optionText":{"div-pay":{"text":"Reading the summary: “Pay now”. Tab moves from the card number field straight past it to the footer.","why":"Bug. No role and not focusable: it is a div pretending to be a button."},"radio-group":{"text":"Tabbing into the ticket options: “Ticket type, grouping, Standing, radio button, checked, 1 of 2”.","why":"Correct behaviour. The fieldset’s legend names the group, and the radio has a name, a role, a state and its position."},"blank-field":{"text":"Tabbing to the email field: “edit text, blank”.","why":"Bug. The field has no accessible name, so the user doesn’t know what to type. It needs a label."},"dialog-open":{"text":"After pressing Book now: “Book Friday pass, dialog, Quantity, spin button, 1”.","why":"Correct behaviour. The dialog is named, announces itself, and focus moved to its first field."},"file-name":{"text":"Arrowing past the venue photo: “graphic, IMG underscore 2041 dot jpeg”.","why":"Bug. The image has no alt attribute, so NVDA reads the file name. Add meaningful alt text, or alt=“” if it is decorative."},"status-update":{"text":"After pressing Add to basket, focus stays on the button and NVDA says: “Ticket added. 2 tickets in your basket.”","why":"Correct behaviour. A polite live region announced the update without moving focus."},"no-state":{"text":"On the FAQ toggle that shows and hides an answer: “How do refunds work?, button”. Pressing it changes nothing in the announcement.","why":"Bug. A disclosure needs aria-expanded so users hear “collapsed” and “expanded”."}}},"words":833,"readMinutes":4}