<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Where accessibility gets complicated — Alexandra Ignatova</title><description>Where accessibility gets complicated. Practical guides on WCAG testing, accessible code, and building inclusive web experiences.</description><link>https://easyally.dev/</link><language>en-us</language><item><title>Quick Tip: Recap Changes with Claude Custom Commands</title><link>https://easyally.dev/blog/claude-code-pr-summary/</link><guid isPermaLink="true">https://easyally.dev/blog/claude-code-pr-summary/</guid><description>Few examples of how I use Claude Code custom commands to recap branch changes, generate PR descriptions, and save time on repetitive tasks.</description><pubDate>Mon, 30 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Have you ever come back to a branch after a meeting, a different project, or a few days off — having no idea what you changed? I know you did.&lt;/p&gt;
&lt;p&gt;You can create a reusable command to simplify your life (a bit) — here is how I did it with Claude Code &lt;code&gt;/summary&lt;/code&gt; and &lt;code&gt;/pr-desc&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;I find it much more useful to get a quick summary of the changes and the reasoning behind them by asking an AI assistant rather than trying to piece it together myself from commit messages and diffs.&lt;/p&gt;
&lt;h2&gt;Custom &lt;code&gt;/summary&lt;/code&gt; command&lt;/h2&gt;
&lt;p&gt;Claude Code supports custom slash commands — markdown files in &lt;code&gt;.claude/commands/&lt;/code&gt; that can act as reusable prompts. I created one for change summaries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.claude/commands/summary.md
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Summarize all changes on the current branch compared to main. Include:

1. A short one-line summary of the overall change
2. A bullet list of each changed file with what was changed and why
3. Any potential concerns or things to watch out for

Use `git diff main --stat` and `git diff main` to get the changes.
Keep it concise and useful.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, anytime I forget what I&apos;ve done in a branch, I type &lt;code&gt;/summary&lt;/code&gt; in Claude Code and get a clear breakdown in a human-readable format — no extra setup, no waiting, and no need to waste time typing the same questions to my AI assistant.&lt;/p&gt;
&lt;p&gt;After creating a new command, you&apos;ll need to restart your Claude Code session for it to show up.&lt;/p&gt;
&lt;h2&gt;Bonus: &lt;code&gt;/pr-desc&lt;/code&gt; command&lt;/h2&gt;
&lt;p&gt;Once I had &lt;code&gt;/summary&lt;/code&gt;, I wanted a second command that formats the output as a ready-to-paste PR description. Same idea, a slightly different structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.claude/commands/pr-desc.md
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Generate a PR description for the current branch compared to main.
Format the output as GitHub-flavored Markdown using this structure:

## Summary
A short 1-3 sentence overview of the change.

## Changes
- **`file-path`** -- what was changed and why

## Concerns
Any potential risks, or &amp;quot;None&amp;quot; if the changes are clean.

Use `git diff main --stat` and `git diff main` to get the changes.
Use `git log main..HEAD --oneline` to see all commits on this branch.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I run &lt;code&gt;/pr-desc&lt;/code&gt; and get a formatted PR description I can paste straight into GitHub.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; You can also instruct the command to copy the output to your clipboard automatically by adding &lt;code&gt;cat /tmp/pr-desc.md | pbcopy&lt;/code&gt; (macOS) to the prompt — then it&apos;s just Cmd+V.&lt;/p&gt;
&lt;p&gt;You can also skip the clipboard entirely and ask Claude Code to create the PR for you — it runs &lt;code&gt;gh pr create&lt;/code&gt; under your GitHub account. But for some reason I prefer to keep this part of the job to myself still :D&lt;/p&gt;
&lt;p&gt;I really like custom commands and use them for many more things beyond branch summaries — for example, I have one for post review where I ask AI to check typos and post structure without changing my personal writing style.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;.claude/commands/&lt;/code&gt; lives in the repo and is committed to git, anyone who clones the project gets the command too — no extra setup needed. Very handy for consistency.&lt;/p&gt;
&lt;p&gt;You can also automate PR summaries with a &lt;a href=&quot;https://github.com/anthropics/claude-code-action&quot; rel=&quot;noreferrer&quot;&gt;Claude Code GitHub Action&lt;/a&gt; that posts a summary comment on every PR automatically (+ additional features). It looks amazing, but as far as I know, this requires an Anthropic API key, and API usage is billed separately from a Claude Pro subscription. I think for solo projects, the custom commands above are simpler.&lt;/p&gt;
</content:encoded><category>AI</category><category>tools</category><category>tips</category><enclosure url="https://easyally.dev/_astro/blog-placeholder.CRrUua7w.png" length="0" type="image/png"/></item><item><title>Can AI Handle Accessibility for Me?</title><link>https://easyally.dev/blog/ai-and-accessibility/</link><guid isPermaLink="true">https://easyally.dev/blog/ai-and-accessibility/</guid><description>Many people assume that AI coding assistants will create accessible components for them. Here are my 2 cents on the issue.</description><pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Can AI tools create &lt;a href=&quot;/blog/accessibility-testing/#why-accessibility-testing-matters&quot;&gt;accessible components&lt;/a&gt; for me already? YES or NO? &lt;br/&gt;
Here are my 2 cents on the issue :)&lt;/p&gt;
&lt;h2&gt;AI and accessible code&lt;/h2&gt;
&lt;p&gt;&lt;span class=&quot;heading__sub&quot;&gt;Is it true that AI models are biased toward inaccessible code?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There might be a fundamental problem with how AI models learn to write code: these models are trained on a huge pool of public code and web content — and most of that code is not very accessible:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://webaim.org/projects/million/2025/&quot; rel=&quot;noreferrer&quot;&gt;The 2025 WebAIM Million report&lt;/a&gt; analyzes the home pages of the top 1,000,000 websites for automatically detectable Web Content Accessibility Guidelines (WCAG) failures.
In 2025, &lt;strong&gt;94.8%&lt;/strong&gt; of home pages had detected WCAG failures.&lt;/p&gt;
&lt;p&gt;The actual number of issues across the web is likely to be higher because automated scans only catch a subset of WCAG criteria.
If the majority of the web is not accessible — AI by default can reproduce exactly the same a11y issues. The old principle still holds: &lt;a href=&quot;https://en.wikipedia.org/wiki/Garbage_in,_garbage_out&quot; rel=&quot;noreferrer&quot;&gt;garbage in, garbage out&lt;/a&gt; — just at a much larger scale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Until the quality of code across the web improves — or developers patiently teach their AI tools using verified accessible components and resources — this problem is likely to stay with us.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;AI coding assistants&lt;/h2&gt;
&lt;p&gt;&lt;span class=&quot;heading__sub&quot;&gt;Own experience&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;By default, AI coding assistants (GitHub Copilot, OpenAI Codex, Claude Code, Cursor etc.) can help generate code that follows basic accessibility principles for simple or typical UI blocks.
But based on my experience, they can also introduce a11y issues.&lt;/p&gt;
&lt;p&gt;Some examples I noticed just recently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding &lt;code&gt;aria-label&lt;/code&gt; to elements that don&apos;t support it&lt;/li&gt;
&lt;li&gt;Overusing ARIA roles&lt;/li&gt;
&lt;li&gt;Creating links and buttons with meaningless labels&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/validate-your-html/#a-common-e-commerce-example-product-cards-wrapped-in-a-link&quot;&gt;Illegally wrapping product/post cards in &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Choosing colors that don&apos;t meet contrast requirements&lt;/li&gt;
&lt;li&gt;Poor keyboard navigation and focus management for more complex components (like dropdowns, carousels, menus etc.)&lt;/li&gt;
&lt;li&gt;Assistive technology announcements are often missing or incorrect (e.g., no &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Guides/Live_regions&quot; rel=&quot;noreferrer&quot;&gt;live region&lt;/a&gt; updates, or wrong ARIA attributes for dynamic content)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Saying just &amp;quot;make this component accessible&amp;quot; during development helps a bit (especially when you ask to &lt;strong&gt;ultrathink&lt;/strong&gt; :)), but is not enough — unfortunately you need to specify in eeevery detail what you want.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://webaim.org/projects/million/2025/&quot; rel=&quot;noreferrer&quot;&gt;The 2025 WebAIM Million report&lt;/a&gt; also highlights increased ARIA usage on pages with higher detected errors.
This can suggest that some developers (and likely AI models) are using ARIA as a band-aid for underlying accessibility issues, which can lead to more repetitive problems.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/aria-role-illustration-mini.MUQFHr7u.png&quot; alt=&quot;A bunny stuffs an accordion into a monitor while others suggest &apos;Try using ARIA role!&apos;&quot; /&gt;
&lt;figcaption&gt;Fixing Accessibility&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;AI tools and models are progressing very fast — about a year ago I saw AI-generated code that regularly contained inaccessible patterns and ARIA attributes that were &lt;strong&gt;not just incorrect but did not even exist (!) in the spec.&lt;/strong&gt;
Today, with the latest models, the situation is noticeably better — the baseline quality of generated HTML has improved.&lt;/p&gt;
&lt;p&gt;But even &amp;quot;noticeably better&amp;quot; is not &amp;quot;accessible.&amp;quot;
I still have to review every component carefully and provide specific instructions to fix issues with semantic HTML, ARIA, keyboard interaction, focus management, and more.
If I did not already know what to look for, those issues would have shipped unnoticed.&lt;/p&gt;
&lt;p&gt;My overall feeling is that most of the time the code looks right, but the experience is sometimes wrong. When AI generates code that looks complete and correct, it is easy to skip the review — especially if you don&apos;t know what to check for.&lt;/p&gt;
&lt;p&gt;As more teams adopt AI coding assistants, a new bottleneck is becoming visible: code creation is no longer the most time-consuming part, but &lt;strong&gt;code review&lt;/strong&gt; is. When code is produced faster than it can be thoroughly reviewed, &lt;strong&gt;accessibility is one of the first things that might get missed.&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/code-review-illustration-mini.CVXfxJNV.png&quot; alt=&quot;A bunny asks for a code review next to a computer buried under carrots, while a girl says &apos;LGTM.&apos;&quot; /&gt;
&lt;figcaption&gt;MRs Stack&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Even &lt;a href=&quot;https://www.anthropic.com/engineering&quot; rel=&quot;noreferrer&quot;&gt;Anthropic&apos;s engineering blog&lt;/a&gt; has some obvious accessibility issues with keyboard navigation, contrast, and valid HTML. I&apos;m sure some of the best engineers work there, which only proves the point: accessibility is very easy to overlook.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/anthropic-contrast-issue-example.Dn809bok.png&quot; alt=&quot;Anthropic&apos;s blog showing a &apos;Skip to footer&apos; link with near-invisible text failing contrast requirements&quot; /&gt;
&lt;figcaption&gt;Example: Skip link contrast issue (March 2026)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;AI-powered accessibility overlays&lt;/h2&gt;
&lt;p&gt;&lt;span class=&quot;heading__sub&quot;&gt;Own experience + research&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;AI-powered accessibility overlays — widgets that claim they can &amp;quot;fix&amp;quot; a website automatically — may also give you a dangerous overconfidence in the state of your solution.&lt;/p&gt;
&lt;p&gt;A lot has been written about this. Here are some of the examples I found most convincing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dl.acm.org/doi/fullHtml/10.1145/3663548.3675650&quot; rel=&quot;noreferrer&quot;&gt;A peer-reviewed study (ACM ASSETS 2024)&lt;/a&gt; surveyed 47 blind and low-vision users. Most reported that overlays made accessibility problems worse — overlay screen readers conflicted with their own assistive technology, and users opted to avoid websites with overlays entirely&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.edf-feph.org/publications/joint-statement-on-accessibility-overlays/&quot; rel=&quot;noreferrer&quot;&gt;Overlays joint statement&lt;/a&gt; by the European Disability Forum and International Association of Accessibility Professionals (IAAP) stating that overlays “do not make the website accessible or compliant with European accessibility legislation”&lt;/li&gt;
&lt;li&gt;Regulators have acted too — in 2025 the &lt;a href=&quot;https://www.ftc.gov/news-events/news/press-releases/2025/04/ftc-approves-final-order-requiring-accessibe-pay-1-million&quot; rel=&quot;noreferrer&quot;&gt;FTC required accessiBe to pay $1 million&lt;/a&gt; over deceptive claims that its AI-powered product could make websites WCAG-compliant&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No overlay can &lt;strong&gt;reliably&lt;/strong&gt; transform non-semantic source code into the semantic structure and interaction model assistive technologies depend on.&lt;/p&gt;
&lt;p&gt;In my experience, for web pages with minor visual issues (like text sizing, color, contrast etc.) an overlay might adjust things well enough — but users can already make the same adjustments through their browser or OS settings. For more complex pages, overlays can interfere with JavaScript behavior, break interactions, and make the experience worse than without them (not to mention the performance issues they can cause).&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/overlay-illustration-mini.DOWjSIze.png&quot; alt=&quot;A bunny presents a cracked monitor held together with band-aids&quot; /&gt;
&lt;figcaption&gt;Solving accessibility problems with a simple installation&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;As a developer, I&apos;d prefer to fix the underlying code. As a user, I&apos;d prefer a website that is accessible from the start — not one that relies on a band-aid I have to work around.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Where does AI help the most?&lt;/h2&gt;
&lt;p&gt;&lt;span class=&quot;heading__sub&quot;&gt;Own experience, scoped to accessibility&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I hope I didn’t come across as too negative about AI tools. Quite the opposite — I’m genuinely optimistic about where they’re headed, and I have to admit I enjoy a good vibe-coding session way more than I probably should :D&lt;/p&gt;
&lt;p&gt;As for me, the biggest benefit right now is that it can significantly &lt;strong&gt;speed up the implementation&lt;/strong&gt; of logic and &lt;strong&gt;well-established&lt;/strong&gt; UI and UX patterns, leaving time to focus on more complex user experience problems.&lt;/p&gt;
&lt;p&gt;In the scope of a11y, AI is also helpful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Drafting alt text&lt;/strong&gt; — AI can describe what is in an image, but you still need to decide what the &lt;em&gt;purpose&lt;/em&gt; of the image is in context (is it decorative? informative? functional?)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explaining WCAG criteria&lt;/strong&gt; — AI is good at translating spec language into practical guidance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generating test configurations&lt;/strong&gt; — CI setups, linter configs, and testing scripts (like the ones in &lt;a href=&quot;/blog/automate-a11y-ci/&quot;&gt;Automate Accessibility Checks in CI&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generating test cases&lt;/strong&gt; — unit and integration tests for accessibility scenarios like keyboard navigation, focus trapping, and ARIA state changes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finding relevant resources&lt;/strong&gt; — documentation, examples, and tools for specific accessibility issues&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brainstorming solutions&lt;/strong&gt; — it can suggest approaches, but you still need to evaluate and test them&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checking against provided checklists&lt;/strong&gt; — you can ask it to review code against a specific WCAG criterion, but you still need to verify the results&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s important to treat AI output as a starting point, not a final answer.&lt;/p&gt;
&lt;h2&gt;So, can AI handle accessibility for me?&lt;/h2&gt;
&lt;p&gt;My answer is...&lt;/p&gt;
&lt;p&gt;Sometimes. But not without human verification (yet).&lt;/p&gt;
&lt;p&gt;At this moment, no tool (that I know about) can answer the question that actually matters: does this work for a real person using a screen reader, a keyboard, or a switch device?
That still requires &lt;a href=&quot;/blog/manual-keyboard-testing/&quot;&gt;keyboard testing&lt;/a&gt;, &lt;a href=&quot;/blog/assistive-technology-testing/&quot;&gt;testing with assistive technologies&lt;/a&gt;, and ideally, feedback from people with disabilities.&lt;/p&gt;
&lt;h3&gt;What we can do with it now:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Learn the fundamentals.&lt;/strong&gt; Understand accessibility principles and best practices so you can review AI-generated code and make informed decisions about what to request, accept, modify, or reject.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tune your AI instructions.&lt;/strong&gt; It’s worth investing time in tuning the project-level AI instructions and rules (e.g., AGENTS.md, SKILL.md) — the more specific and detailed your instructions, the better the output will be. It’s quite a task — perfect for the &lt;span class=&quot;strikethrough&quot; role=&quot;presentation&quot;&gt;pickiest&lt;/span&gt;&lt;span class=&quot;visually-hidden&quot;&gt; or should I say,&lt;/span&gt; detail-obsessed and experienced person on the team. You can also try out skills created by other contributors and adapt them to your project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Invest in automation.&lt;/strong&gt; A few years ago, a mid-sized team could get by without a strong automation pipeline. Now that AI can produce code faster than anyone can review it, automated checks — linters, CI, accessibility testing — are a safety net you can’t skip.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these take effort and slow you down at first.&lt;/p&gt;
&lt;p&gt;That&apos;s why the question: are we moving faster than our ability to keep up with &lt;span class=&quot;strikethrough&quot; role=&quot;presentation&quot;&gt;accessibility&lt;/span&gt;&lt;span class=&quot;visually-hidden&quot;&gt; or should I say,&lt;/span&gt; quality? (rhetorical question)&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/skills-illustration-mini.BpAEWY0b.png&quot; alt=&quot;A girl leans on a huge book &apos;all the things you should avoid&apos; while a bunny presents a tiny paper saying &apos;do good things, do not do bad things&apos;&quot; /&gt;
&lt;figcaption&gt;When experience makes a difference&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/blog/run-automated-tools/&quot;&gt;Run Automated Accessibility Tools&lt;/a&gt; — browser extensions and manual tools&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/automate-a11y-ci/&quot;&gt;Automate Accessibility Checks in CI&lt;/a&gt; — set up automated validation in your pipeline&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/accessibility-testing/&quot;&gt;Practical accessibility testing&lt;/a&gt; — the full testing process&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.modernleader.is/p/when-code-stops-being-the-bottleneck&quot; rel=&quot;noreferrer&quot;&gt;When code stops being the bottleneck&lt;/a&gt; by Kelly Vaughn — on the shift from code creation to code review as the new bottleneck&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Accessibility</category><category>AI</category><category>tools</category><enclosure url="https://easyally.dev/_astro/aria-role-illustration-mini.MUQFHr7u.png" length="0" type="image/png"/></item><item><title>Automate Accessibility Checks in CI</title><link>https://easyally.dev/blog/automate-a11y-ci/</link><guid isPermaLink="true">https://easyally.dev/blog/automate-a11y-ci/</guid><description>Set up automated HTML validation and WCAG 2.2 AA accessibility checks in your CI pipeline with html-validate, pa11y-ci, husky, and GitHub Actions — step by step.</description><pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You know that &lt;a href=&quot;/blog/run-automated-tools/&quot;&gt;automated tools&lt;/a&gt; exist.
Now let&apos;s wire some of them into our development workflow so every push and every pull request gets checked automatically — no manual clicks, no forgotten scans.&lt;/p&gt;
&lt;p&gt;We will look into 2 levels of automated checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTML validation&lt;/strong&gt; on every push (pre-push hook)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WCAG 2.2 AA accessibility checks&lt;/strong&gt; on every pull request (GitHub Actions)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both running against our actual built pages.&lt;/p&gt;
&lt;p&gt;This guide uses an Astro project with pnpm as an example, but &lt;strong&gt;html-validate&lt;/strong&gt; and &lt;strong&gt;pa11y-ci&lt;/strong&gt; work with any static site or framework. Adapt the build and serve commands to your stack.&lt;/p&gt;
&lt;h2&gt;Install the tools&lt;/h2&gt;
&lt;p&gt;You need 3 packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;html-validate&lt;/strong&gt; — lints built HTML files for structural issues, invalid ARIA, and spec violations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pa11y-ci&lt;/strong&gt; — runs WCAG (Web Content Accessibility Guidelines) accessibility checks against rendered pages using a headless browser&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;husky&lt;/strong&gt; — manages git hooks (pre-push, pre-commit)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pnpm add -D html-validate pa11y-ci husky
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;pa11y-ci&lt;/strong&gt; needs a running server to check pages. Since Astro has a built-in preview server (&lt;code&gt;astro preview&lt;/code&gt;), we use that. Adapt the serve command to your framework&apos;s preview server.&lt;/p&gt;
&lt;p&gt;Initialize husky:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx husky init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a &lt;code&gt;.husky/&lt;/code&gt; directory and adds a &lt;code&gt;prepare&lt;/code&gt; script to your &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Configure html-validate&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;.htmlvalidate.json&lt;/code&gt; in your project root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;extends&amp;quot;: [&amp;quot;html-validate:recommended&amp;quot;],
  &amp;quot;rules&amp;quot;: {
    &amp;quot;no-inline-style&amp;quot;: &amp;quot;off&amp;quot;,
    &amp;quot;no-trailing-whitespace&amp;quot;: &amp;quot;off&amp;quot;,
    &amp;quot;long-title&amp;quot;: &amp;quot;warn&amp;quot;,
    &amp;quot;void-style&amp;quot;: &amp;quot;off&amp;quot;,
    &amp;quot;no-raw-characters&amp;quot;: &amp;quot;off&amp;quot;,
    &amp;quot;attr-case&amp;quot;: [&amp;quot;error&amp;quot;, { &amp;quot;ignoreForeign&amp;quot;: true }]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why these overrides?&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;caption&gt;Disabled html-validate rules and reasons&lt;/caption&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;Rule&lt;/th&gt;&lt;th scope=&quot;col&quot;&gt;Why off/warn&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;no-inline-style&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Syntax highlighting (Shiki, Prism) generates inline styles in code blocks&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;no-trailing-whitespace&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Build tools and formatters may leave trailing spaces — not worth blocking on&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;long-title&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Some page titles exceed the recommended length — warn instead of error&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;void-style&lt;/code&gt;&lt;/th&gt;&lt;td&gt;MDX renderers may output &lt;code&gt;{&apos;&lt;hr/&gt;&apos;}&lt;/code&gt; instead of &lt;code&gt;{&apos;&lt;hr&gt;&apos;}&lt;/code&gt; — both are valid&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;no-raw-characters&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Markdown-rendered content may contain &lt;code&gt;{&apos;&gt;&apos;}&lt;/code&gt; in text — valid in HTML&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;attr-case&lt;/code&gt;&lt;/th&gt;&lt;td&gt;SVG attributes like &lt;code&gt;viewBox&lt;/code&gt; use camelCase — &lt;code&gt;ignoreForeign&lt;/code&gt; allows this&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Run it against your build output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pnpm build
npx html-validate &apos;dist/**/*.html&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you first run &lt;strong&gt;html-validate&lt;/strong&gt;, expect it to find real issues — like &lt;code&gt;aria-label&lt;/code&gt; on elements that don&apos;t support it, missing &lt;code&gt;type&lt;/code&gt; on buttons, or invalid nesting. Fix these before configuring rules to suppress them.&lt;/p&gt;
&lt;h2&gt;Configure pa11y-ci&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;.pa11yci.json&lt;/code&gt; in your project root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;defaults&amp;quot;: {
    &amp;quot;standard&amp;quot;: &amp;quot;WCAG2AA&amp;quot;,
    &amp;quot;timeout&amp;quot;: 10000
  },
  &amp;quot;sitemap&amp;quot;: &amp;quot;http://localhost:4321/sitemap-0.xml&amp;quot;,
  &amp;quot;sitemapFind&amp;quot;: &amp;quot;https://your-domain.com&amp;quot;,
  &amp;quot;sitemapReplace&amp;quot;: &amp;quot;http://localhost:4321&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of listing URLs manually, &lt;strong&gt;pa11y-ci&lt;/strong&gt; reads your sitemap and discovers all pages automatically. &lt;code&gt;sitemapFind&lt;/code&gt; and &lt;code&gt;sitemapReplace&lt;/code&gt; rewrite the production URLs to point at your local server.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WCAG2AA&lt;/code&gt; is the standard most accessibility laws require (EU&apos;s EN 301 549, US Section 508). Change to &lt;code&gt;WCAG2AAA&lt;/code&gt; if you want stricter checks.&lt;/p&gt;
&lt;p&gt;To test locally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pnpm build
pnpm preview &amp;amp; npx pa11y-ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To check a single page during development, you can run &lt;code&gt;pa11y&lt;/code&gt; directly without the CI config:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx pa11y http://localhost:4321/blog/your-page/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Set up the hook to validate HTML on every push&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;.husky/pre-push&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm build &amp;amp;&amp;amp; npx html-validate &apos;dist/**/*.html&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every &lt;code&gt;git push&lt;/code&gt; now builds your site and validates the HTML. If validation fails, the push is blocked.&lt;/p&gt;
&lt;h3&gt;Why only html-validate in the hook?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;pa11y-ci&lt;/strong&gt; needs to start a server, launch a headless browser, and crawl every page — noticeably slower than &lt;strong&gt;html-validate&lt;/strong&gt;, which just reads files from disk. Running the full a11y check in CI is my personal preference — you can also add it to the pre-push hook. Either way, keep it in CI — hooks can be skipped, CI is the last line of defense before code gets merged.&lt;/p&gt;
&lt;h2&gt;Create the GitHub Actions workflow&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;.github/workflows/validate.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: HTML &amp;amp; Accessibility Validation

on:
  pull_request:
    branches: [main]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

      - run: pnpm install

      - name: Build website
        run: pnpm build

      - name: HTML validation
        run: pnpm validate:html

      - name: Start server &amp;amp; run a11y checks
        run: |
          # Override pa11y-ci config to use system Chrome
          cat &amp;gt; .pa11yci.json &amp;lt;&amp;lt;EOF
          {
            &amp;quot;defaults&amp;quot;: {
              &amp;quot;standard&amp;quot;: &amp;quot;WCAG2AA&amp;quot;,
              &amp;quot;timeout&amp;quot;: 10000,
              &amp;quot;chromeLaunchConfig&amp;quot;: {
                &amp;quot;executablePath&amp;quot;: &amp;quot;/usr/bin/google-chrome-stable&amp;quot;,
                &amp;quot;args&amp;quot;: [&amp;quot;--no-sandbox&amp;quot;]
              }
            },
            &amp;quot;sitemap&amp;quot;: &amp;quot;http://localhost:4321/sitemap-0.xml&amp;quot;,
            &amp;quot;sitemapFind&amp;quot;: &amp;quot;https://your-domain.com&amp;quot;,
            &amp;quot;sitemapReplace&amp;quot;: &amp;quot;http://localhost:4321&amp;quot;
          }
          EOF

          pnpm preview &amp;amp;
          SERVER_PID=\$!

          # Wait for server to be ready
          for i in $(seq 1 10); do
            curl -s http://localhost:4321/ &amp;gt; /dev/null &amp;amp;&amp;amp; break
            sleep 1
          done

          pnpm validate:a11y
          EXIT_CODE=\$?

          kill \$SERVER_PID
          exit \$EXIT_CODE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What this does:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Builds the website&lt;/li&gt;
&lt;li&gt;Runs &lt;strong&gt;html-validate&lt;/strong&gt; against all HTML files&lt;/li&gt;
&lt;li&gt;Overrides the &lt;strong&gt;pa11y-ci&lt;/strong&gt; config to use system Chrome (needed in CI)&lt;/li&gt;
&lt;li&gt;Starts the preview server, waits for it, runs &lt;strong&gt;pa11y-ci&lt;/strong&gt; against all pages from the sitemap, then cleans up&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Add the npm scripts to your &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;validate:html&amp;quot;: &amp;quot;html-validate &apos;dist/**/*.html&apos;&amp;quot;,
    &amp;quot;validate:a11y&amp;quot;: &amp;quot;pa11y-ci&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What you now have&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;html-validate&lt;/strong&gt; catches structural HTML issues on every &lt;code&gt;git push&lt;/code&gt; (pre-push hook)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pa11y-ci&lt;/strong&gt; runs WCAG 2.2 AA checks against every page on every pull request (GitHub Actions)&lt;/li&gt;
&lt;li&gt;Both run against your actual built pages, not source code — so they catch what users will see&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remember: This won&apos;t replace &lt;a href=&quot;/blog/manual-keyboard-testing/&quot;&gt;manual testing with keyboard&lt;/a&gt; and &lt;a href=&quot;/blog/assistive-technology-testing/&quot;&gt;assistive technologies&lt;/a&gt;. But they catch them consistently, on every change, without anyone needing to remember.&lt;/p&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Can I use axe-core instead of pa11y-ci?&lt;/h3&gt;
&lt;p&gt;Yes. &lt;a href=&quot;https://npmx.dev/package/@axe-core/cli&quot;&gt;@axe-core/cli&lt;/a&gt; is a good alternative. &lt;strong&gt;pa11y-ci&lt;/strong&gt; has stronger multi-page CI support out of the box, including URL-list and sitemap-based runs with CI-friendly failure behavior, while axe-core is the widely adopted accessibility engine behind Deque’s axe tooling and influences Lighthouse accessibility scoring.&lt;/p&gt;
&lt;h3&gt;What about Lighthouse CI?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-ci&quot;&gt;Lighthouse CI&lt;/a&gt; is strong when you want Lighthouse’s broader audit coverage — performance, SEO, best practices, and accessibility — in one CI workflow. It can be more involved than an accessibility-only runner, especially if you want uploaded reports, historical tracking, or GitHub status checks, but those are optional. For accessibility-only checks, &lt;strong&gt;pa11y-ci&lt;/strong&gt; is typically simpler.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/blog/run-automated-tools/&quot;&gt;Run Automated Accessibility Tools&lt;/a&gt; — overview of browser extensions and manual tools&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/validate-your-html/&quot;&gt;Validate Your HTML&lt;/a&gt; — why valid markup matters&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/accessibility-testing/&quot;&gt;Practical accessibility testing&lt;/a&gt; — the full testing process&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Accessibility</category><category>testing</category><category>tools</category><category>Continuous Integration</category><enclosure url="https://easyally.dev/_astro/blog-placeholder.CRrUua7w.png" length="0" type="image/png"/></item><item><title>Make Your Videos Accessible</title><link>https://easyally.dev/blog/video-a11y-requirements/</link><guid isPermaLink="true">https://easyally.dev/blog/video-a11y-requirements/</guid><description>A practical guide to WCAG 2.2 video accessibility: how to add captions, audio descriptions, transcripts, accessible controls, and handle autoplay — with code examples.</description><pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here&apos;s everything you need to make your video accessible — from captions to transcripts.&lt;/p&gt;
&lt;p&gt;If you need a reminder what accessibility levels A, AA, and AAA mean, check the FAQ section first: &lt;a href=&quot;#what-do-a-aa-aaa-mean&quot;&gt;What do A, AA, AAA mean?&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Provide captions (A)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ask the question:&lt;/strong&gt; Does the video have speech or other audio that is needed to understand the content? If yes, you need captions. (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/captions-prerecorded.html&quot;&gt;WCAG 1.2.2&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;You&apos;ll need a properly formatted &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API&quot;&gt;WebVTT&lt;/a&gt; (Web Video Text Tracks) file (&lt;code&gt;.vtt&lt;/code&gt;).&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/VTT_illustration_mini.DZavUcG0.png&quot; alt=&quot;A girl in a fur hat adds a .vtt caption file to a video on an old computer so the person who cannot hear can follow along&quot; /&gt;
&lt;/figure&gt;
&lt;h3&gt;How to add captions&lt;/h3&gt;
&lt;p&gt;Add a &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; element with &lt;code&gt;kind=&amp;quot;captions&amp;quot;&lt;/code&gt; and a language tag:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;video controls&amp;gt;
  &amp;lt;source src=&amp;quot;video.mp4&amp;quot; type=&amp;quot;video/mp4&amp;quot;&amp;gt;
  &amp;lt;track src=&amp;quot;captions_en.vtt&amp;quot; kind=&amp;quot;captions&amp;quot; srclang=&amp;quot;en&amp;quot; label=&amp;quot;English&amp;quot; default&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A basic &lt;code&gt;captions_en.vtt&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WEBVTT

00:00:01.000 --&amp;gt; 00:00:04.000
[Narrator]: Welcome to the tutorial.

00:00:05.500 --&amp;gt; 00:00:09.000
Today we&apos;ll learn how to make
video content accessible.

00:00:10.000 --&amp;gt; 00:00:13.500
[background music]
&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;vtt-description&quot;&gt;The file starts with &lt;code&gt;WEBVTT&lt;/code&gt;, followed by caption cues — each with a timecode range and the text to display. Use square brackets for non-speech sounds like &lt;code&gt;[background music]&lt;/code&gt; or speaker identification like &lt;code&gt;[Narrator]&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Provide alternatives (A)&lt;/h2&gt;
&lt;p&gt;&lt;span class=&quot;heading__sub&quot;&gt;Alternatives for audio-only and video-only content&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ask the question:&lt;/strong&gt; Is your media audio-only (e.g., a podcast) or video-only (e.g., a silent tutorial with background music)? (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/audio-only-and-video-only-prerecorded.html&quot;&gt;WCAG 1.2.1&lt;/a&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Audio-only&lt;/strong&gt; (e.g., a podcast, voice recording) — provide a &lt;strong&gt;text transcript&lt;/strong&gt; of everything that is spoken and any meaningful sounds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Video-only&lt;/strong&gt; (e.g., a tutorial with no narration) — provide a &lt;strong&gt;text alternative&lt;/strong&gt; that describes the visual content, OR an &lt;strong&gt;audio description&lt;/strong&gt; track that narrates what&apos;s shown&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In both cases, the alternative must be &lt;strong&gt;equivalent&lt;/strong&gt; — a user who can&apos;t hear the audio or see the video should get the same information.&lt;/p&gt;
&lt;h3&gt;Video-only example&lt;/h3&gt;
&lt;p&gt;Imagine a video showing how to set up an aquarium — no narration, just background music and visual steps. The text alternative must describe each step in enough detail: what equipment to place, where to position it, how much water to add. A vague summary like &lt;em&gt;&amp;quot;This video shows how to set up an aquarium&amp;quot;&lt;/em&gt; is &lt;strong&gt;not enough&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Place the text near the video and use &lt;code&gt;aria-describedby&lt;/code&gt; to link them — screen readers will announce that a description is available. A clear heading on the text block also helps users find it via heading navigation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;video controls aria-describedby=&amp;quot;aquarium-setup-steps&amp;quot;&amp;gt;
  &amp;lt;source src=&amp;quot;aquarium-setup.mp4&amp;quot; type=&amp;quot;video/mp4&amp;quot;&amp;gt;
  &amp;lt;track src=&amp;quot;captions_en.vtt&amp;quot; kind=&amp;quot;captions&amp;quot; srclang=&amp;quot;en&amp;quot; label=&amp;quot;English&amp;quot;&amp;gt;
&amp;lt;/video&amp;gt;

&amp;lt;div id=&amp;quot;aquarium-setup-steps&amp;quot;&amp;gt;
  &amp;lt;h3&amp;gt;Step-by-step aquarium setup&amp;lt;/h3&amp;gt;
  &amp;lt;ol&amp;gt;
    &amp;lt;li&amp;gt;Rinse the gravel under running water and spread it evenly across the tank bottom.&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;Place the filter and heater against the back wall, leaving space between them.&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;Fill the tank slowly with dechlorinated water to avoid disturbing the gravel.&amp;lt;/li&amp;gt;
  &amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For longer descriptions, &lt;code&gt;aria-details&lt;/code&gt; is a more semantically precise alternative to &lt;code&gt;aria-describedby&lt;/code&gt; — it signals a detailed description rather than a brief summary. Browser support is growing but not yet universal.&lt;/p&gt;
&lt;p&gt;Even if the audio isn&apos;t meaningful, it&apos;s still good practice to provide a captions track with &lt;code&gt;[background music]&lt;/code&gt; — so users don&apos;t think you forgot captions. See &lt;a href=&quot;#what-if-my-video-has-only-background-music&quot;&gt;What if my video has only background music?&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Add audio descriptions (A, AA)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ask the question:&lt;/strong&gt; Does the video have visual information that is needed to understand the content? (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/audio-description-or-media-alternative-prerecorded.html&quot;&gt;WCAG 1.2.3&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/audio-description-prerecorded.html&quot;&gt;WCAG 1.2.5&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;If your video shows key visuals (e.g., actions, diagrams, on-screen text) that aren&apos;t described in the narration or dialogue, you &lt;strong&gt;must provide audio description&lt;/strong&gt;. It describes the visual information needed to understand the content, &lt;strong&gt;including text displayed in the video&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;At &lt;strong&gt;Level A&lt;/strong&gt; (WCAG 1.2.3), you can choose between audio description &lt;strong&gt;or&lt;/strong&gt; a full text alternative that describes everything in the video. At &lt;strong&gt;Level AA&lt;/strong&gt; (WCAG 1.2.5), audio description is specifically required — a text alternative alone is not enough. If your video has static visuals (e.g., a talking head against an unchanging background), a text alternative may be sufficient even at AA — see &lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Techniques/general/G203&quot;&gt;Technique G203 (W3C)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What does audio description sound like? During a pause in dialogue, a separate narrator&apos;s voice describes the visuals — for example: &lt;em&gt;&amp;quot;The presenter points to a bar chart. The tallest bar is labeled &apos;Dark mode&apos; at 60%.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/Video_description_illustration_mini.hwzr0p5d.png&quot; alt=&quot;The girl in a fur hat describes a video to a fish in a bowl: &quot;Okay, I will describe it for you... A person is cleaning an aquarium&quot;&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The best way to handle description is often not to need it at all — integrate all visual information into the main audio. This is called &lt;strong&gt;&amp;quot;integrated description&amp;quot;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example, instead of a narrator saying &lt;em&gt;&amp;quot;As you can see here...&amp;quot;&lt;/em&gt; while showing a chart, the narrator would say &lt;em&gt;&amp;quot;This bar chart shows that 60% of users prefer dark mode.&amp;quot;&lt;/em&gt; — the visual information is already in the spoken words, so no separate audio description is needed.&lt;/p&gt;
&lt;h3&gt;How to provide descriptions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Integrate description into the main audio content&lt;/strong&gt; (recommended)&lt;/li&gt;
&lt;li&gt;OR provide a second audio track (&lt;code&gt;kind=&amp;quot;descriptions&amp;quot;&lt;/code&gt;) or a separate video version with narration included&lt;/li&gt;
&lt;li&gt;Add a way for users to select or toggle the audio description&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;track src=&amp;quot;descriptions_en.vtt&amp;quot; kind=&amp;quot;descriptions&amp;quot; srclang=&amp;quot;en&amp;quot; label=&amp;quot;Audio Description&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that browsers don&apos;t expose this track via controls, so you&apos;ll need to build a custom toggle UI or provide a narrated version of the video.&lt;/p&gt;
&lt;h2&gt;Provide custom controls (A)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ask the question:&lt;/strong&gt; Are you building a custom video player on top of the native &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element? (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html&quot;&gt;WCAG 2.1.1&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;If you are using a ready-to-use video player library (e.g., Video.js, Plyr, or a platform like YouTube/Vimeo embeds), it likely already handles most of these concerns.&lt;/p&gt;
&lt;p&gt;The HTML5 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element supports basic accessibility, including keyboard access to controls and support for captions/subtitles.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/Video_custom_control_illustration_mini.CVjqP15v.png&quot; alt=&quot;A fish in a bowl taps a keyboard to operate custom video controls with CC, play, skip, and volume buttons&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;However, it has &lt;strong&gt;limitations&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Inconsistent appearance across different browsers and operating systems&lt;/li&gt;
&lt;li&gt;No custom focus styles&lt;/li&gt;
&lt;li&gt;No transcript support or toggling of audio descriptions via the interface&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Building accessible controls&lt;/h3&gt;
&lt;p&gt;If you need more control, replace the default &lt;code&gt;controls&lt;/code&gt; with your own &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements and JavaScript:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Provide keyboard support (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html&quot;&gt;Understanding Keyboard Accessible&lt;/a&gt;) — see also &lt;a href=&quot;/blog/manual-keyboard-testing/&quot;&gt;Test Keyboard Navigation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Make the keyboard focus indicator visible (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html&quot;&gt;Understanding Focus Visible&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Provide clear labels (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html&quot;&gt;Understanding Labels or Instructions&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Have sufficient contrast between colors for text, controls, and backgrounds (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html&quot;&gt;Understanding Contrast&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For recommendations on building custom video players: &lt;a href=&quot;https://www.w3.org/WAI/media/av/player/&quot;&gt;Media Players (W3C WAI)&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Allow stop of auto-playing (A)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ask the question:&lt;/strong&gt; Does your video play automatically? (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html&quot;&gt;WCAG 2.2.2&lt;/a&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Avoid autoplay&lt;/strong&gt; whenever possible&lt;/li&gt;
&lt;li&gt;If autoplay is required, &lt;strong&gt;provide controls&lt;/strong&gt; to stop or pause the video&lt;/li&gt;
&lt;li&gt;If using muted autoplay (e.g., a hero background video), users must still be able to pause it. Also consider &lt;code&gt;prefers-reduced-motion&lt;/code&gt; — some users find any motion distracting or disorienting&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/autoplay_video_illustration_mini.DN2df8TZ.png&quot; alt=&quot;A startled fish and scared bunnies react to a loud autoplaying video ad — a stop or pause button is highlighted as a possible solution if autoplay is unfortunately required&quot; /&gt;
&lt;/figure&gt;
&lt;h2&gt;Provide a transcript (AAA)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ask the question:&lt;/strong&gt; Do you want to provide the best possible access to your video content? (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/media-alternative-prerecorded.html&quot;&gt;WCAG 1.2.8&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Transcripts for &lt;strong&gt;audio-only&lt;/strong&gt; content (e.g., podcasts) are required at &lt;strong&gt;Level A&lt;/strong&gt; under &lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/audio-only-and-video-only-prerecorded.html&quot;&gt;WCAG 1.2.1&lt;/a&gt;. This section is about transcripts for &lt;strong&gt;synchronized media&lt;/strong&gt; (video with audio) — that&apos;s Level AAA.&lt;/p&gt;
&lt;p&gt;Even if you already have captions, a separate transcript adds value:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Captions&lt;/strong&gt; enable people who are Deaf or hard of hearing to watch the video and read along at the same time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transcripts&lt;/strong&gt; provide access to people who are Deaf-blind and use braille, and are also used by people without disabilities&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;How to add a transcript&lt;/h3&gt;
&lt;p&gt;Include a full text transcript of the video content. Link to it near the video, display it in a toggleable section, or just include it in the page content below the video.&lt;/p&gt;
&lt;p&gt;The simplest approach is the native &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element — it handles expand/collapse and accessibility without any JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;
**Read transcript**

  &amp;lt;p&amp;gt;[Narrator]: Welcome to the tutorial...&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;Today we&apos;ll learn how to make video content accessible.&amp;lt;/p&amp;gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&apos;s how it works — try clicking:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read transcript&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;[Narrator]: Welcome to the tutorial. Today we&apos;ll learn how to make video content accessible.&lt;/p&gt;
  &lt;p&gt;[Narrator]: First, let&apos;s add captions to our video using a WebVTT file.&lt;/p&gt;
  &lt;p&gt;[background music]&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;What if my video has only background music?&lt;/h3&gt;
&lt;p&gt;Background music alone doesn&apos;t require full captions — but you should still let users know, so they don&apos;t think you forgot to add them.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://easyally.dev/_astro/no_dialog_video_illustration_mini.DhMa9_UI.png&quot; alt=&quot;A fish with glasses watches an aquarium video with no dialog, just background music — the caption track shows &quot;[background lounge music]&quot; to let viewers know no captions or description are needed&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;You can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Provide a captions file with only the appropriate indication, such as &lt;code&gt;[background music]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Provide the information in text with the video, such as: &lt;em&gt;&amp;quot;Captions not needed: The only sound in this video is background music&amp;quot;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the video still conveys information visually (e.g., a tutorial or demonstration), you need a text alternative instead — see &lt;a href=&quot;#provide-alternatives-a&quot;&gt;Provide alternatives&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Captions vs. Subtitles&lt;/h3&gt;
&lt;p&gt;These terms are often used interchangeably, but they serve different purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Captions&lt;/strong&gt; are intended for viewers who &lt;strong&gt;cannot hear&lt;/strong&gt; the audio. They include not just dialogue but also sound effects, music cues, and speaker identification — e.g., &lt;code&gt;[door slams]&lt;/code&gt;, &lt;code&gt;[upbeat music]&lt;/code&gt;, &lt;code&gt;[Narrator]:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subtitles&lt;/strong&gt; are intended for viewers who &lt;strong&gt;can hear&lt;/strong&gt; but don&apos;t understand the spoken language. They typically only include dialogue.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For accessibility, you need &lt;strong&gt;captions&lt;/strong&gt;, not just subtitles.&lt;/p&gt;
&lt;h3&gt;Closed captions vs. Open captions&lt;/h3&gt;
&lt;p&gt;There are two ways captions can be delivered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Closed captions&lt;/strong&gt; are a separate text track that users can turn on or off. This is the most common approach on the web — using &lt;code&gt;&amp;lt;track kind=&amp;quot;captions&amp;quot;&amp;gt;&lt;/code&gt; or a player&apos;s built-in caption toggle (the &lt;strong&gt;CC&lt;/strong&gt; button). Closed captions give users control over whether they see the text.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open captions&lt;/strong&gt; are burned directly into the video frames. They are always visible and cannot be turned off. &lt;strong&gt;This can be useful when you cannot rely on the player supporting text tracks (e.g., social media videos, video you share in .PDF etc.)&lt;/strong&gt;, but it removes user choice and makes the captions impossible to resize or restyle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;For web accessibility, closed captions are preferred&lt;/strong&gt; because they let users control the display, work with assistive technologies, and can be provided in multiple languages.&lt;/p&gt;
&lt;h3&gt;What do A, AA, AAA mean?&lt;/h3&gt;
&lt;p&gt;Each requirement in this article is marked with a WCAG conformance level. Levels are cumulative — to meet &lt;strong&gt;Level AA&lt;/strong&gt;, you must satisfy all A &lt;strong&gt;and&lt;/strong&gt; AA requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Level A&lt;/strong&gt; — the bare minimum. Without these, some users simply cannot access the content at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Level AA&lt;/strong&gt; — the standard most organizations target. Includes all Level A requirements, plus additional ones. This is what most accessibility laws and policies require (e.g., EU&apos;s EN 301 549, US Section 508).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Level AAA&lt;/strong&gt; — the highest level. Includes all A and AA requirements, plus additional ones. Not always feasible for all content, but provides the best experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/media/av/&quot;&gt;Making Audio and Video Media Accessible (W3C WAI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/accessibility-testing/&quot;&gt;Practical accessibility testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/assistive-technology-testing/&quot;&gt;Test with screen readers &amp;amp; system settings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Accessibility</category><category>Web Content Accessibility Guidelines</category><category>video</category><enclosure url="https://easyally.dev/_astro/VTT_illustration_mini.DZavUcG0.png" length="0" type="image/png"/></item><item><title>Test with Screen Readers &amp; System Settings</title><link>https://easyally.dev/blog/assistive-technology-testing/</link><guid isPermaLink="true">https://easyally.dev/blog/assistive-technology-testing/</guid><description>Learn how to test your website with screen readers, zoom, high contrast, and reduced motion. Covers VoiceOver, NVDA, and OS-level accessibility preferences.</description><pubDate>Wed, 18 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is part 4 of the &lt;a href=&quot;/blog/accessibility-testing/&quot;&gt;Accessibility Testing&lt;/a&gt; series. It covers how to test your pages with screen readers, zoom, high contrast mode, and reduced motion preferences.&lt;/p&gt;
&lt;h2&gt;VoiceOver (macOS)&lt;/h2&gt;
&lt;p&gt;VoiceOver is the native screen reader on macOS — no installation needed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Starting:&lt;/strong&gt; &lt;code&gt;Cmd + F5&lt;/code&gt; or use &lt;code&gt;System Settings → Accessibility → VoiceOver&lt;/code&gt; toggle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Settings:&lt;/strong&gt; VoiceOver Utility&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;caption&gt;Essential VoiceOver keyboard commands&lt;/caption&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;Task&lt;/th&gt;&lt;th scope=&quot;col&quot;&gt;Command&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Start (or stop) VoiceOver&lt;/th&gt;&lt;td&gt;&lt;code&gt;Cmd + F5&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;VoiceOver activation keys (VO keys)&lt;/th&gt;&lt;td&gt;&lt;code&gt;Control + Option&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Start reading&lt;/th&gt;&lt;td&gt;&lt;code&gt;VO + A&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Pause or resume reading&lt;/th&gt;&lt;td&gt;&lt;code&gt;Control&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Open Rotor&lt;/th&gt;&lt;td&gt;&lt;code&gt;VO + U&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Activate link&lt;/th&gt;&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt; or &lt;code&gt;VO + Space Bar&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Activate button&lt;/th&gt;&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt; or &lt;code&gt;Space Bar&lt;/code&gt; or &lt;code&gt;VO + Space Bar&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Read next/previous item&lt;/th&gt;&lt;td&gt;&lt;code&gt;VO + Right&lt;/code&gt; or &lt;code&gt;Left Arrow&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Repeat last spoken phrase&lt;/th&gt;&lt;td&gt;&lt;code&gt;VO + Z&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Go to next heading&lt;/th&gt;&lt;td&gt;&lt;code&gt;VO + Cmd + H&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Go to next table&lt;/th&gt;&lt;td&gt;&lt;code&gt;VO + Cmd + T&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Go to previous (heading, table, etc.)&lt;/th&gt;&lt;td&gt;&lt;code&gt;VO + Shift + Cmd + H&lt;/code&gt;, &lt;code&gt;T&lt;/code&gt;, etc.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Navigate table cells&lt;/th&gt;&lt;td&gt;&lt;code&gt;VO + Arrow Keys&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Interact with (enter/exit) groups and objects&lt;/th&gt;&lt;td&gt;&lt;code&gt;VO + Shift + Down/Up Arrow&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;VoiceOver uses an &amp;quot;interaction&amp;quot; model — some elements (like toolbars, tables, or groups) require you to &amp;quot;enter&amp;quot; them with &lt;code&gt;VO + Shift + Down Arrow&lt;/code&gt; before you can navigate their contents. Use &lt;code&gt;VO + Shift + Up Arrow&lt;/code&gt; to exit back out.&lt;/p&gt;
&lt;p&gt;For the full reference: &lt;a href=&quot;https://dequeuniversity.com/screenreaders/voiceover-keyboard-shortcuts&quot;&gt;VoiceOver Keyboard Shortcuts on a Mac&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;NVDA (Windows)&lt;/h2&gt;
&lt;p&gt;NVDA is a free, open-source screen reader for Windows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Download:&lt;/strong&gt; &lt;a href=&quot;https://www.nvaccess.org/download/&quot;&gt;nvaccess.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Starting:&lt;/strong&gt; &lt;code&gt;Ctrl + Alt + N&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speech viewer:&lt;/strong&gt; NVDA icon → Tools → Speech viewer (useful for seeing what NVDA announces)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The NVDA modifier key is &lt;code&gt;Insert&lt;/code&gt; by default (can be changed to &lt;code&gt;Caps Lock&lt;/code&gt; in settings).&lt;/p&gt;
&lt;table&gt;
&lt;caption&gt;Essential NVDA keyboard commands&lt;/caption&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;Task&lt;/th&gt;&lt;th scope=&quot;col&quot;&gt;Command&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Start NVDA&lt;/th&gt;&lt;td&gt;&lt;code&gt;Ctrl + Alt + N&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Stop NVDA&lt;/th&gt;&lt;td&gt;&lt;code&gt;Insert + Q&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Start reading&lt;/th&gt;&lt;td&gt;&lt;code&gt;Insert + Down Arrow&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Pause or resume reading&lt;/th&gt;&lt;td&gt;&lt;code&gt;Control&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Read next/previous item&lt;/th&gt;&lt;td&gt;&lt;code&gt;Down Arrow&lt;/code&gt; / &lt;code&gt;Up Arrow&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Activate link or button&lt;/th&gt;&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Next heading&lt;/th&gt;&lt;td&gt;&lt;code&gt;H&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Next landmark&lt;/th&gt;&lt;td&gt;&lt;code&gt;D&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Next table&lt;/th&gt;&lt;td&gt;&lt;code&gt;T&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Navigate table cells&lt;/th&gt;&lt;td&gt;&lt;code&gt;Ctrl + Alt + Arrow Keys&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Elements list (like VoiceOver&apos;s Rotor)&lt;/th&gt;&lt;td&gt;&lt;code&gt;Insert + F7&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Toggle browse/focus mode&lt;/th&gt;&lt;td&gt;&lt;code&gt;Insert + Space&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;NVDA has two modes: &lt;strong&gt;browse mode&lt;/strong&gt; (for reading content, single-letter navigation like &lt;code&gt;H&lt;/code&gt; for headings) and &lt;strong&gt;focus mode&lt;/strong&gt; (for interacting with form controls). NVDA switches automatically in most cases, but you can toggle manually with &lt;code&gt;Insert + Space&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For the full reference: &lt;a href=&quot;https://dequeuniversity.com/screenreaders/nvda-keyboard-shortcuts&quot;&gt;NVDA Keyboard Shortcuts&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;What to check&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Enable a screen reader and navigate through the page using only the keyboard.&lt;/li&gt;
&lt;li&gt;As you move through the page, verify:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reading order&lt;/strong&gt; — Is the content announced in a logical sequence?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Images&lt;/strong&gt; — Are meaningful images described? Are decorative images hidden from the screen reader?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Headings&lt;/strong&gt; — Do headings reflect the page structure? Are any levels skipped?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Form fields&lt;/strong&gt; — Does each input have a label that is announced?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive states&lt;/strong&gt; — Are states like &amp;quot;expanded,&amp;quot; &amp;quot;pressed,&amp;quot; &amp;quot;checked,&amp;quot; or &amp;quot;selected&amp;quot; announced when they change?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic content&lt;/strong&gt; — Are updates (toasts, error messages, loading states) announced via live regions?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Links&lt;/strong&gt; — Do link texts make sense out of context? (No &amp;quot;click here&amp;quot; or &amp;quot;read more&amp;quot; without context)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Quick structure audit with the Rotor&lt;/h3&gt;
&lt;p&gt;The VoiceOver Rotor (&lt;code&gt;VO + U&lt;/code&gt;) and NVDA Elements List (&lt;code&gt;Insert + F7&lt;/code&gt;) give you a quick overview of the page structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check &lt;strong&gt;Headings&lt;/strong&gt; — verify the hierarchy is correct and nothing is skipped&lt;/li&gt;
&lt;li&gt;Check &lt;strong&gt;Landmarks&lt;/strong&gt; — verify main regions (&lt;code&gt;banner&lt;/code&gt;, &lt;code&gt;navigation&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;contentinfo&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Check &lt;strong&gt;Links&lt;/strong&gt; — confirm all meaningful links have descriptive names&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a fast way to spot missing headings, broken landmark regions, or links with generic names.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;TalkBack (Android) testing will be covered in a separate mobile testing guide (coming soon).&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Zoom and screen magnification&lt;/h2&gt;
&lt;p&gt;Users with low vision often zoom in to 200% or more. WCAG requires that content remains usable at these levels.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html&quot;&gt;WCAG 1.4.4 Resize Text&lt;/a&gt; — content must be readable at &lt;strong&gt;200% zoom&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/reflow.html&quot;&gt;WCAG 1.4.10 Reflow&lt;/a&gt; — content must reflow into a single column at &lt;strong&gt;400% zoom&lt;/strong&gt; (no horizontal scrolling)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;How to test&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Browser zoom to 200%&lt;/strong&gt; — &lt;code&gt;Cmd + +&lt;/code&gt; (macOS) or &lt;code&gt;Ctrl + +&lt;/code&gt; (Windows). Check that no text is cut off, no elements overlap, and all functionality is still available.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reflow at 400%&lt;/strong&gt; — open DevTools responsive mode and set the viewport to &lt;strong&gt;320px wide&lt;/strong&gt; (this simulates 400% zoom on a 1280px screen). Verify there is no horizontal scrollbar and all content is reachable by scrolling vertically.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;What to look for&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Text is not truncated or hidden behind other elements&lt;/li&gt;
&lt;li&gt;Interactive elements are still reachable and usable&lt;/li&gt;
&lt;li&gt;No horizontal scrolling appears (except for data tables, maps, or diagrams)&lt;/li&gt;
&lt;li&gt;Layout adapts without loss of content or functionality&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;High contrast and forced colors&lt;/h2&gt;
&lt;p&gt;Some users rely on high contrast modes that override your CSS colors. Content and UI must remain visible and functional.&lt;/p&gt;
&lt;h3&gt;How to test&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows&lt;/strong&gt; — &lt;code&gt;Settings → Accessibility → Contrast themes&lt;/code&gt; and select a high contrast theme. Or press &lt;code&gt;Left Alt + Left Shift + Print Screen&lt;/code&gt; to toggle quickly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;macOS&lt;/strong&gt; — &lt;code&gt;System Settings → Accessibility → Display → Increase contrast&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevTools&lt;/strong&gt; — In Chrome, open DevTools → Rendering panel → &amp;quot;Emulate CSS media feature &lt;code&gt;prefers-contrast&lt;/code&gt;&amp;quot; → set to &lt;code&gt;more&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forced colors&lt;/strong&gt; — In Chrome DevTools → Rendering → &amp;quot;Emulate CSS media feature &lt;code&gt;forced-colors&lt;/code&gt;&amp;quot; → set to &lt;code&gt;active&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What to look for&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Focus indicators are still visible (borders, outlines)&lt;/li&gt;
&lt;li&gt;Icons and SVGs are not invisible (they may lose their fill/stroke colors)&lt;/li&gt;
&lt;li&gt;Custom-styled buttons and inputs remain distinguishable&lt;/li&gt;
&lt;li&gt;Information conveyed by color alone is still accessible (use borders, text labels, or patterns as well)&lt;/li&gt;
&lt;li&gt;CSS that uses &lt;code&gt;@media (forced-colors: active)&lt;/code&gt; provides fallbacks where needed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Reduced motion&lt;/h2&gt;
&lt;p&gt;Some users experience discomfort, dizziness, or seizures from animations. WCAG requires that motion can be disabled.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/three-flashes-or-below-threshold.html&quot;&gt;WCAG 2.3.1 Three Flashes or Below Threshold&lt;/a&gt; — no content flashes more than 3 times per second&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html&quot;&gt;WCAG 2.2.2 Pause, Stop, Hide&lt;/a&gt; — auto-playing content can be paused, stopped, or hidden&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;How to test&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;macOS&lt;/strong&gt; — &lt;code&gt;System Settings → Accessibility → Display → Reduce motion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows&lt;/strong&gt; — &lt;code&gt;Settings → Accessibility → Visual effects → Animation effects → Off&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevTools&lt;/strong&gt; — In Chrome, open DevTools → Rendering panel → &amp;quot;Emulate CSS media feature &lt;code&gt;prefers-reduced-motion&lt;/code&gt;&amp;quot; → set to &lt;code&gt;reduce&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What to look for&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Animations are reduced or removed when &lt;code&gt;prefers-reduced-motion: reduce&lt;/code&gt; is active&lt;/li&gt;
&lt;li&gt;Carousels and auto-playing content do not auto-advance&lt;/li&gt;
&lt;li&gt;Page transitions are instant or very short&lt;/li&gt;
&lt;li&gt;No content flashes more than 3 times per second (regardless of motion preference)&lt;/li&gt;
&lt;li&gt;Essential motion (like a progress bar filling) is still allowed — only decorative or non-essential animation should be removed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Voice Control quick tip: Label in Name&lt;/h2&gt;
&lt;p&gt;Voice Control (macOS, iOS, Android) lets users interact by speaking the visible text of buttons and links — e.g., &lt;em&gt;&amp;quot;click Search&amp;quot;&lt;/em&gt;. This breaks when the accessible name doesn&apos;t match what&apos;s on screen.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html&quot;&gt;WCAG 2.5.3 Label in Name&lt;/a&gt; — the accessible name must &lt;strong&gt;contain&lt;/strong&gt; the visible text label&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, if a button visually says &lt;strong&gt;Search&lt;/strong&gt; but has &lt;code&gt;aria-label=&amp;quot;Find on the website&amp;quot;&lt;/code&gt;, a Voice Control user saying &lt;em&gt;&amp;quot;click Search&amp;quot;&lt;/em&gt; will get no response — the accessible name doesn&apos;t include the word &amp;quot;Search.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; if an element already has a visible text label, either start &lt;code&gt;aria-label&lt;/code&gt; with that same text or don&apos;t use &lt;code&gt;aria-label&lt;/code&gt; at all. Let the visible text be the accessible name.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Bad: visible label says &amp;quot;Search&amp;quot; but accessible name is different --&amp;gt;
&amp;lt;button aria-label=&amp;quot;Find on the website&amp;quot;&amp;gt;Search&amp;lt;/button&amp;gt;

&amp;lt;!-- Good: no aria-label, visible text is the accessible name --&amp;gt;
&amp;lt;button&amp;gt;Search&amp;lt;/button&amp;gt;

&amp;lt;!-- Good: aria-label starts with the visible text --&amp;gt;
&amp;lt;button aria-label=&amp;quot;Search products&amp;quot;&amp;gt;Search&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;A full Voice Control testing guide will be covered in a separate post.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; &lt;a href=&quot;/blog/video-a11y-requirements/&quot;&gt;Video accessibility requirements&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Accessibility</category><category>testing</category><category>screen reader</category><enclosure url="https://easyally.dev/_astro/blog-placeholder.CRrUua7w.png" length="0" type="image/png"/></item><item><title>Test Keyboard Navigation</title><link>https://easyally.dev/blog/manual-keyboard-testing/</link><guid isPermaLink="true">https://easyally.dev/blog/manual-keyboard-testing/</guid><description>Keyboard accessibility is essential for users who cannot use a mouse. Learn how to test keyboard navigation, focus management, and common interaction patterns.</description><pubDate>Mon, 16 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is part 3 of the &lt;a href=&quot;/blog/accessibility-testing/&quot;&gt;Accessibility Testing&lt;/a&gt; series. It covers how to test that every interactive element on your page is reachable and usable with a keyboard alone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;macOS users:&lt;/strong&gt; Keyboard navigation is &lt;strong&gt;NOT enabled by default&lt;/strong&gt;. Your test results will be inaccurate if you don&apos;t enable it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;System Settings&lt;/strong&gt; → Keyboard → turn on &lt;strong&gt;Keyboard navigation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safari only:&lt;/strong&gt; Safari → Settings → Advanced → turn on &lt;strong&gt;Press Tab to highlight each item on a webpage&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Key WCAG criteria&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html&quot;&gt;&lt;strong&gt;2.1.1 Keyboard&lt;/strong&gt;&lt;/a&gt; — all functionality must be operable via keyboard&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/no-keyboard-trap.html&quot;&gt;&lt;strong&gt;2.1.2 No Keyboard Trap&lt;/strong&gt;&lt;/a&gt; — users must be able to navigate away from any component&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html&quot;&gt;&lt;strong&gt;2.4.1 Bypass Blocks&lt;/strong&gt;&lt;/a&gt; — provide a way to skip repeated navigation (skip links)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html&quot;&gt;&lt;strong&gt;2.4.3 Focus Order&lt;/strong&gt;&lt;/a&gt; — focus order must be logical and match visual layout&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html&quot;&gt;&lt;strong&gt;2.4.7 Focus Visible&lt;/strong&gt;&lt;/a&gt; — focused elements must have a visible indicator&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Standard keyboard interactions&lt;/h2&gt;
&lt;table&gt;
&lt;caption&gt;Keyboard keys and their actions&lt;/caption&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;Key&lt;/th&gt;&lt;th scope=&quot;col&quot;&gt;Action&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;Tab&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Move focus to next interactive element&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;Shift + Tab&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Move focus to previous interactive element&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;Enter&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Activate links and buttons, submit forms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;Space&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Activate buttons, toggle checkboxes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;Arrow Keys&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Navigate within composite widgets (tabs, menus, radio groups)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;Escape&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Close/dismiss modals, dropdowns, tooltips, menus&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;&lt;code&gt;Home&lt;/code&gt; / &lt;code&gt;End&lt;/code&gt;&lt;/th&gt;&lt;td&gt;Jump to first/last item in a list or menu&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Although WCAG doesn&apos;t explicitly require Enter or Space for activation, it recommends that custom controls mimic native keyboard behavior to meet user expectations — otherwise, you must supply clear instructions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Search for &amp;quot;Keyboard interactions&amp;quot; in the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles&quot;&gt;MDN ARIA role documentation&lt;/a&gt; to find the official keyboard pattern for any role.&lt;/p&gt;
&lt;h2&gt;How to test&lt;/h2&gt;
&lt;p&gt;Put your mouse aside and navigate the page using only the keyboard. As you go, check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skip link works&lt;/strong&gt; — press &lt;code&gt;Tab&lt;/code&gt; once on a fresh page load. A &amp;quot;Skip to main content&amp;quot; link should appear. Pressing &lt;code&gt;Enter&lt;/code&gt; should move focus past the navigation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All interactive elements are reachable&lt;/strong&gt; — can you &lt;code&gt;Tab&lt;/code&gt; to every link, button, input, and control?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus order is logical&lt;/strong&gt; — does focus move in a sequence that matches the visual layout? No unexpected jumps?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus indicator is visible&lt;/strong&gt; — can you always tell which element has focus? The indicator should be clearly visible, not just a subtle color change.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No keyboard traps&lt;/strong&gt; — can you navigate in and out of every component? (Modals are an intentional exception — see below.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hidden content is not focusable&lt;/strong&gt; — elements that are visually hidden (&lt;code&gt;display: none&lt;/code&gt;, &lt;code&gt;visibility: hidden&lt;/code&gt;, &lt;code&gt;hidden&lt;/code&gt; attribute, or inside a closed &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt;) should not receive focus.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All actions are possible&lt;/strong&gt; — can you open menus, close dropdowns, submit forms, dismiss notifications, and interact with every feature without a mouse?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;code&gt;tabindex&lt;/code&gt; pitfalls&lt;/h2&gt;
&lt;p&gt;A common source of keyboard issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tabindex=&amp;quot;0&amp;quot;&lt;/code&gt; — adds an element to the natural tab order. Use this for custom interactive elements that aren&apos;t natively focusable.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tabindex=&amp;quot;-1&amp;quot;&lt;/code&gt; — removes from tab order but allows programmatic focus (e.g., &lt;code&gt;element.focus()&lt;/code&gt;). Useful for elements that should receive focus only via scripts.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tabindex&lt;/code&gt; &amp;gt; 0 — &lt;strong&gt;avoid this&lt;/strong&gt;. It overrides the natural DOM order and creates unpredictable navigation for keyboard users.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Common patterns to test&lt;/h2&gt;
&lt;h3&gt;Modals and dialogs&lt;/h3&gt;
&lt;p&gt;Modals require special attention. When open, focus must be trapped inside — users should not be able to &lt;code&gt;Tab&lt;/code&gt; past the modal to the page behind it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the modal with the keyboard
&lt;ul&gt;
&lt;li&gt;Focus moves into the modal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Tab&lt;/code&gt; through all elements in the modal
&lt;ul&gt;
&lt;li&gt;Focus stays trapped inside the modal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Close the modal with &lt;code&gt;Escape&lt;/code&gt; or a close button
&lt;ul&gt;
&lt;li&gt;Focus returns to the element that opened it&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The native HTML &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element with &lt;code&gt;showModal()&lt;/code&gt; handles focus trapping automatically and is the recommended approach over custom implementations.&lt;/p&gt;
&lt;h3&gt;Dropdown menus&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Enter&lt;/code&gt; or &lt;code&gt;Space&lt;/code&gt; opens the menu&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Arrow Keys&lt;/code&gt; move between menu items&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Escape&lt;/code&gt; closes the menu and returns focus to the trigger&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Tab&lt;/code&gt; from the last item closes the menu&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Tab panels&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Tab&lt;/code&gt; moves focus to the active tab&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Arrow Keys&lt;/code&gt; move between tabs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Tab&lt;/code&gt; from a tab moves focus into the tab panel content&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Accordions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Enter&lt;/code&gt; or &lt;code&gt;Space&lt;/code&gt; expands/collapses a section&lt;/li&gt;
&lt;li&gt;Content inside a collapsed section is not focusable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; &lt;a href=&quot;/blog/assistive-technology-testing/&quot;&gt;Assistive technology testing&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Accessibility</category><category>testing</category><category>keyboard</category><enclosure url="https://easyally.dev/_astro/blog-placeholder.CRrUua7w.png" length="0" type="image/png"/></item><item><title>Run Automated Accessibility Tools</title><link>https://easyally.dev/blog/run-automated-tools/</link><guid isPermaLink="true">https://easyally.dev/blog/run-automated-tools/</guid><description>Automated tools establish a baseline for accessibility testing. Learn how to use axe DevTools, Lighthouse, WAVE, and integrate checks into your CI pipeline.</description><pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is part 2 of the &lt;a href=&quot;/blog/accessibility-testing/&quot;&gt;Accessibility Testing&lt;/a&gt; series. All tools mentioned here are &lt;strong&gt;free&lt;/strong&gt; — these are the ones I find most useful and use &lt;strong&gt;myself&lt;/strong&gt;. They help catch accessibility issues automatically — in the browser, in your editor, and in CI.&lt;/p&gt;
&lt;h2&gt;Why use automated tools?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Catch common issues fast&lt;/strong&gt; — color contrast failures, missing alt text, invalid ARIA roles, broken label associations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check what&apos;s hard to spot manually&lt;/strong&gt; — contrast ratios, computed accessible names, ARIA attribute validity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Establish a baseline&lt;/strong&gt; — automated checks ensure basic standards are met before you start manual testing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prevent regressions&lt;/strong&gt; — CI integration catches new issues before they reach production&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Tools cannot check all accessibility aspects automatically. Human judgement is required. Sometimes evaluation tools can produce false or misleading results. Web accessibility evaluation tools can not determine accessibility, they can only assist in doing so.&lt;/p&gt;
&lt;p&gt;— &lt;a href=&quot;https://www.w3.org/WAI/test-evaluate/tools/selecting/&quot;&gt;W3C WAI: Selecting Web Accessibility Evaluation Tools&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To see just how far automated tools can miss the mark, read Manuel Matuzovic&apos;s &lt;a href=&quot;https://www.matuzo.at/blog/building-the-most-inaccessible-site-possible-with-a-perfect-lighthouse-score/&quot; rel=&quot;noreferrer&quot;&gt;Building the most inaccessible site possible with a perfect Lighthouse score&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;axe DevTools&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.deque.com/axe/devtools/&quot;&gt;axe DevTools&lt;/a&gt; is one of the most popular accessibility testing browser extensions. It scans the page and reports issues by severity (Critical, Serious, Moderate, Minor).&lt;/p&gt;
&lt;p&gt;Install axe DevTools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/axe-devtools/lhdoppojpmngadmnindnejefpokejbdd&quot;&gt;Chrome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/&quot;&gt;Firefox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/axe-devtools/kcenlimkmjjkdfcaleembgmldppmeaih&quot;&gt;Edge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How to use:&lt;/strong&gt; Open DevTools, go to the &amp;quot;axe DevTools&amp;quot; tab, and click &amp;quot;Scan&amp;quot;. Review the issues list — each issue links to the affected element and explains how to fix it.&lt;/p&gt;
&lt;h2&gt;Lighthouse&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/&quot;&gt;Lighthouse&lt;/a&gt; is built into Chrome DevTools and uses axe-core under the hood for its accessibility audits.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;In the browser&lt;/strong&gt; — DevTools → Lighthouse tab → check &amp;quot;Accessibility&amp;quot; → click &amp;quot;Analyze page load&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLI&lt;/strong&gt; — &lt;code&gt;npx lighthouse https://your-site.com --only-categories=accessibility&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI&lt;/strong&gt; — use &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-ci&quot;&gt;Lighthouse CI&lt;/a&gt; to run audits on every pull request and fail the build if the score drops&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lighthouse gives you a score out of 100, but remember — a perfect score does not mean your site is accessible. It means the automated checks passed.&lt;/p&gt;
&lt;h2&gt;WAVE&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://wave.webaim.org/&quot;&gt;WAVE&lt;/a&gt; is another excellent tool from WebAIM. It provides a visual overlay showing accessibility issues, structural elements, and ARIA information directly on the page.&lt;/p&gt;
&lt;p&gt;Install WAVE:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/wave-evaluation-tool/jbbplnpkjmmeebjpijfedlgcdilocofh&quot;&gt;Chrome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/wave-accessibility-tool/&quot;&gt;Firefox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/wave-evaluation-tool/khapceneeednkiopkkbgkibbdoajpkoj&quot;&gt;Edge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WAVE is particularly useful for its &lt;strong&gt;Structure&lt;/strong&gt; view, which shows heading hierarchy and landmark regions at a glance.&lt;/p&gt;
&lt;h2&gt;CI integration with axe-core&lt;/h2&gt;
&lt;p&gt;The real power of automated testing is catching regressions before they ship. &lt;a href=&quot;https://github.com/dequelabs/axe-core&quot;&gt;axe-core&lt;/a&gt; — the engine behind axe DevTools and Lighthouse — can be integrated directly into your test suite:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://npmx.dev/package/@axe-core/playwright&quot;&gt;@axe-core/playwright&lt;/a&gt;&lt;/strong&gt; — run axe checks inside Playwright tests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://npmx.dev/package/@axe-core/cli&quot;&gt;@axe-core/cli&lt;/a&gt;&lt;/strong&gt; — run axe from the command line against any URL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://npmx.dev/package/cypress-axe&quot;&gt;cypress-axe&lt;/a&gt;&lt;/strong&gt; — run axe checks inside Cypress tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some frameworks also offer dev-time axe integration — for example, &lt;strong&gt;&lt;a href=&quot;https://nuxt.com/modules/a11y&quot;&gt;@nuxt/a11y&lt;/a&gt;&lt;/strong&gt; surfaces violations directly in Nuxt DevTools as you develop, so you can catch issues before they even reach CI.&lt;/p&gt;
&lt;p&gt;This lets you fail a build when new automated-detectable issues are introduced. It won&apos;t replace manual testing with &lt;a href=&quot;/blog/manual-keyboard-testing/&quot;&gt;keyboard&lt;/a&gt; and &lt;a href=&quot;/blog/assistive-technology-testing/&quot;&gt;assistive technologies&lt;/a&gt; — but it prevents known issues from shipping unnoticed.&lt;/p&gt;
&lt;p&gt;For a step-by-step guide to setting up HTML validation and WCAG checks in your CI pipeline, see &lt;a href=&quot;/blog/automate-a11y-ci/&quot;&gt;Automate Accessibility Checks in CI&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Other useful tools&lt;/h2&gt;
&lt;h3&gt;WebAIM Contrast Checker&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://webaim.org/resources/contrastchecker/&quot;&gt;WebAIM Contrast Checker&lt;/a&gt; is useful when you need to verify color contrast ratios against WCAG requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WCAG AA&lt;/strong&gt; requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WCAG AAA&lt;/strong&gt; requires at least 7:1 for normal text and 4.5:1 for large text&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Browser DevTools&lt;/h3&gt;
&lt;p&gt;Both Chrome and Firefox have built-in accessibility tools — no extensions needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chrome&lt;/strong&gt; — &lt;code&gt;Elements → Accessibility&lt;/code&gt; to inspect the accessibility tree, computed ARIA attributes, and accessible names.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Firefox&lt;/strong&gt; — &lt;code&gt;F12 → Accessibility&lt;/code&gt; tab. Firefox&apos;s Accessibility Inspector highlights issues directly on the page and can check for keyboard/text label/contrast issues across the entire page at once.&lt;/p&gt;
&lt;p&gt;For testing user preferences (reduced motion, forced colors, contrast, color blindness), see the &lt;a href=&quot;/blog/assistive-technology-testing/&quot;&gt;Assistive Technology Testing&lt;/a&gt; post.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; &lt;a href=&quot;/blog/manual-keyboard-testing/&quot;&gt;Test keyboard navigation&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Accessibility</category><category>testing</category><category>tools</category><enclosure url="https://easyally.dev/_astro/blog-placeholder.CRrUua7w.png" length="0" type="image/png"/></item><item><title>Validate Your HTML</title><link>https://easyally.dev/blog/validate-your-html/</link><guid isPermaLink="true">https://easyally.dev/blog/validate-your-html/</guid><description>HTML validation is the first step in accessibility testing. Learn why valid markup matters and how to catch issues early.</description><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is part 1 of the &lt;a href=&quot;/blog/accessibility-testing/&quot;&gt;Accessibility Testing&lt;/a&gt; series. HTML validation comes first because invalid markup can cause automated tools to miss issues or report false positives — and it affects how assistive technologies interpret your page.&lt;/p&gt;
&lt;h2&gt;Why valid HTML matters&lt;/h2&gt;
&lt;p&gt;Assistive technologies rely on the browser&apos;s DOM to build the accessibility tree. When your HTML is invalid, the browser has to guess how to parse it — and different browsers guess differently. The result: screen readers may skip content, announce elements incorrectly, or miss interactive controls entirely.&lt;/p&gt;
&lt;p&gt;Invalid HTML also causes SSR (Server-Side Rendering) bugs and unexpected UX problems. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nesting a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; inside an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; creates two nested interactive elements. Screen readers may only announce the outer one, or create separate tab stops that make no sense to keyboard users.&lt;/li&gt;
&lt;li&gt;Nesting a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; inside a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; is invalid — browsers silently close the outer &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; and create unexpected DOM structure. In frameworks like React, Vue, or Astro this can also cause hydration mismatches.&lt;/li&gt;
&lt;li&gt;Nesting a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; inside another &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; has the same problem — the browser auto-closes the outer paragraph, splitting your content in ways you won&apos;t see visually but screen readers will.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A common e-commerce example: product cards wrapped in a link&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Wrapping product cards in a link is tempting, but be aware — it might be ILLEGAL&lt;/strong&gt; 🚨&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A pattern you&apos;ll see often in e-commerce: wrapping an entire product card in an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag. This becomes a problem when the card contains other interactive elements — links, buttons, checkboxes, tooltips. &lt;a href=&quot;https://www.w3.org/TR/html401/struct/links.html#h-12.2.2&quot;&gt;Nested links are illegal per the HTML spec&lt;/a&gt;, and nesting other interactive elements inside a link leads to invalid HTML and unpredictable browser behavior. If you&apos;re using SSR, this can also trigger hydration warnings.&lt;/p&gt;
&lt;p&gt;For a deeper look at how browsers handle these situations, see Vadim Makeev&apos;s article &lt;a href=&quot;https://pepelsbey.dev/articles/jumping-html-tags/&quot; rel=&quot;noreferrer&quot;&gt;Jumping HTML Tags&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Not sure if one element can be nested inside another? Use &lt;a href=&quot;https://caninclude.onrender.com/&quot;&gt;caninclude&lt;/a&gt; — a quick tool that tells you whether a specific HTML nesting is valid.&lt;/p&gt;
&lt;h2&gt;How to validate&lt;/h2&gt;
&lt;p&gt;Use the &lt;a href=&quot;https://validator.w3.org/nu/&quot;&gt;W3C Markup Validation Service&lt;/a&gt; to check your page or component HTML. You can validate by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;URL&lt;/strong&gt; — paste the live URL of your page&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File upload&lt;/strong&gt; — upload the HTML file&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text input&lt;/strong&gt; — paste your HTML snippet directly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The validator will flag both &lt;strong&gt;errors&lt;/strong&gt; (must fix) and &lt;strong&gt;warnings&lt;/strong&gt; (should consider). For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Warning:&lt;/strong&gt; &lt;em&gt;Consider adding a &lt;code&gt;lang&lt;/code&gt; attribute to the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; start tag to declare the language of this document.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error:&lt;/strong&gt; &lt;em&gt;The element &lt;code&gt;button&lt;/code&gt; must not appear as a descendant of the &lt;code&gt;a&lt;/code&gt; element.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Validation in your development workflow&lt;/h3&gt;
&lt;p&gt;You don&apos;t have to leave your editor to validate. There are tools that catch HTML issues as you code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://html-validate.org/&quot;&gt;html-validate&lt;/a&gt;&lt;/strong&gt; — an npm linter for HTML that can run in CI or as a pre-commit hook&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/jsx-eslint/eslint-plugin-jsx-a11y&quot;&gt;eslint-plugin-jsx-a11y&lt;/a&gt;&lt;/strong&gt; — ESLint plugin that catches accessibility and markup issues in JSX/TSX&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Browser extensions&lt;/strong&gt; — the &lt;a href=&quot;https://validator.w3.org/nu/&quot;&gt;W3C Nu Html Checker&lt;/a&gt; also offers a browser bookmarklet for quick checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Catching issues during development is always cheaper than finding them in production.&lt;/p&gt;
&lt;h2&gt;What to look for&lt;/h2&gt;
&lt;p&gt;Focus on fixing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Invalid nesting — elements inside parents they don&apos;t belong in (&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; in &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; in &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Missing required attributes — &lt;code&gt;alt&lt;/code&gt; on images (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html&quot;&gt;WCAG 1.1.1&lt;/a&gt;), &lt;code&gt;lang&lt;/code&gt; on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/language-of-page.html&quot;&gt;WCAG 3.1.1&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Duplicate &lt;code&gt;id&lt;/code&gt; attributes — these break label associations and anchor links&lt;/li&gt;
&lt;li&gt;Unclosed elements — can cause the browser to build a different DOM than you intended&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; &lt;a href=&quot;/blog/run-automated-tools/&quot;&gt;Run automated tools&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Accessibility</category><category>testing</category><category>HTML</category><enclosure url="https://easyally.dev/_astro/blog-placeholder.CRrUua7w.png" length="0" type="image/png"/></item><item><title>Practical Accessibility Testing for Developers</title><link>https://easyally.dev/blog/accessibility-testing/</link><guid isPermaLink="true">https://easyally.dev/blog/accessibility-testing/</guid><description>A step-by-step process for accessibility testing that covers the minimal steps you need to follow to make sure your feature or page meets WCAG 2.2 standards.</description><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This guide covers the &lt;strong&gt;basic&lt;/strong&gt; steps of accessibility testing for software engineers — enough to make sure your feature or page meets &lt;a href=&quot;https://www.w3.org/TR/WCAG22/&quot;&gt;WCAG (Web Content Accessibility Guidelines) 2.2&lt;/a&gt; standards (AA Level) in most scenarios. More advanced topics like Voice Control and mobile testing will be covered in a separate guide.&lt;/p&gt;
&lt;h2&gt;Why accessibility testing matters&lt;/h2&gt;
&lt;p&gt;If you’ve never heard of accessibility (a11y), the simplest way to think about it is this: it means building a website that works well for everyone — people using keyboards, people on slow connections, people with temporary injuries, and people using assistive technologies every day. Accessibility is not just a compliance checkbox. It’s a sign of a &lt;strong&gt;quality website.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;WCAG 2.2 AA is the standard most organizations should aim for. It is a legal requirement in many jurisdictions (EAA (European Accessibility Act) in the EU, ADA (Americans with Disabilities Act) in the US, etc.).&lt;/p&gt;
&lt;h2&gt;How to do basic accessibility testing&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;/blog/validate-your-html/&quot;&gt;Validate your HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/run-automated-tools/&quot;&gt;Run automated tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/manual-keyboard-testing/&quot;&gt;Test keyboard navigation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/assistive-technology-testing/&quot;&gt;Test with assistive technologies&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each step builds on the previous one.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tools cannot check all accessibility aspects automatically. Human judgement is required.&lt;/p&gt;
&lt;p&gt;Sometimes evaluation tools can produce false or misleading results. Web accessibility evaluation tools can not determine accessibility, they can only assist in doing so.&lt;/p&gt;
&lt;p&gt;— &lt;a href=&quot;https://www.w3.org/WAI/test-evaluate/tools/selecting/&quot;&gt;W3C WAI: Selecting Web Accessibility Evaluation Tools&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Screen readers used in this guide&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;macOS&lt;/strong&gt; — VoiceOver (built-in)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows&lt;/strong&gt; — NVDA (free, &lt;a href=&quot;https://www.freedomscientific.com/products/software/jaws/&quot;&gt;JAWS&lt;/a&gt; is also widely used but requires a paid license)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; &lt;a href=&quot;/blog/video-a11y-requirements/&quot;&gt;Video accessibility requirements&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Accessibility</category><category>testing</category><category>Web Content Accessibility Guidelines</category><enclosure url="https://easyally.dev/_astro/blog-placeholder.CRrUua7w.png" length="0" type="image/png"/></item></channel></rss>