<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Zdenek Spacek</title>
    <description>The latest articles on Forem by Zdenek Spacek (@primestark).</description>
    <link>https://forem.com/primestark</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3768823%2Fba72d4fa-0518-4596-8e52-d428126b2a8b.jpg</url>
      <title>Forem: Zdenek Spacek</title>
      <link>https://forem.com/primestark</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/primestark"/>
    <language>en</language>
    <item>
      <title>The April 2026 ADA Deadline: What Developers Actually Need to Know</title>
      <dc:creator>Zdenek Spacek</dc:creator>
      <pubDate>Mon, 16 Feb 2026 08:46:13 +0000</pubDate>
      <link>https://forem.com/primestark/the-april-2026-ada-deadline-what-developers-actually-need-to-know-42fk</link>
      <guid>https://forem.com/primestark/the-april-2026-ada-deadline-what-developers-actually-need-to-know-42fk</guid>
      <description>&lt;p&gt;The internet is full of panicky articles about the April 24, 2026 ADA deadline. Most of them are trying to sell you something. Here's what's actually happening, who it affects, and what you should do about it. From a developer's perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the Deadline?
&lt;/h2&gt;

&lt;p&gt;On April 24, 2026, state and local government entities serving populations of 50,000+ must have web content and mobile apps that conform to &lt;strong&gt;WCAG 2.1 Level AA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Smaller entities (under 50,000) get until &lt;strong&gt;April 26, 2028&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is a DOJ rule under &lt;strong&gt;Title II of the ADA&lt;/strong&gt; (which covers government entities).&lt;/p&gt;

&lt;h2&gt;
  
  
  Does This Apply to Private Businesses?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Not directly.&lt;/strong&gt; The April 2026 rule specifically targets government entities.&lt;/p&gt;

&lt;p&gt;But here's the nuance that matters: &lt;strong&gt;Title III of the ADA&lt;/strong&gt; covers "places of public accommodation". And courts have increasingly ruled that websites fall into this category. Over 4,000 ADA website accessibility lawsuits were filed against private businesses in 2023 alone.&lt;/p&gt;

&lt;p&gt;The April deadline doesn't create new obligations for private businesses. But it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sets WCAG 2.1 AA as the explicit standard (previously it was implied)&lt;/li&gt;
&lt;li&gt;Increases public awareness&lt;/li&gt;
&lt;li&gt;Gives plaintiff attorneys more ammunition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build websites for clients, expect more questions about this.&lt;/p&gt;

&lt;h2&gt;
  
  
  For EU Developers: You're Already Late
&lt;/h2&gt;

&lt;p&gt;The European Accessibility Act (EAA) deadline was &lt;strong&gt;June 28, 2025&lt;/strong&gt;. It's already passed. The EAA applies to ALL private businesses selling products or services in the EU. If your client has EU customers, they should already be compliant.&lt;/p&gt;

&lt;h2&gt;
  
  
  What WCAG 2.1 AA Actually Requires
&lt;/h2&gt;

&lt;p&gt;Skip the 78-page spec. Here's what matters for developers:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Big 5 (fix these and you're 70% there)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alt text on images&lt;/strong&gt; — Every non-decorative &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; needs &lt;code&gt;alt&lt;/code&gt;. Decorative images get &lt;code&gt;alt=""&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Form labels&lt;/strong&gt; — Every &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; needs an associated &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; or &lt;code&gt;aria-label&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Color contrast&lt;/strong&gt; — Text needs 4.5:1 ratio against background (3:1 for large text). Use &lt;a href="https://webaim.org/resources/contrastchecker/" rel="noopener noreferrer"&gt;WebAIM's contrast checker&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keyboard navigation&lt;/strong&gt; — Every interactive element must be reachable and usable via keyboard alone. Tab order should be logical.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Heading hierarchy&lt;/strong&gt; — Use &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; through &lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt; in order. Don't skip levels. Don't use headings just for styling.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Next 5 (for AA compliance)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Language declaration&lt;/strong&gt; — &lt;code&gt;&amp;lt;html lang="en"&amp;gt;&lt;/code&gt; (or appropriate language code)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link purpose&lt;/strong&gt; — Links should make sense out of context. No "click here."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error identification&lt;/strong&gt; — Form errors must be clearly described in text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resize support&lt;/strong&gt; — Content usable at 200% zoom without horizontal scrolling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus visibility&lt;/strong&gt; — Interactive elements must have visible focus indicators.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What NOT to Do
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Don't Install an Accessibility Widget
&lt;/h3&gt;

&lt;p&gt;AccessiBe was fined $1 million by the FTC in 2024 for making false claims about their AI-powered accessibility widget. Overlay widgets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't fix underlying code issues&lt;/li&gt;
&lt;li&gt;Can interfere with actual assistive technology&lt;/li&gt;
&lt;li&gt;Give a false sense of compliance&lt;/li&gt;
&lt;li&gt;Won't protect you in a lawsuit&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Don't Panic-Buy an Enterprise Tool
&lt;/h3&gt;

&lt;p&gt;If you're a solo dev or small agency, you don't need Siteimprove ($5k+/year) or Level Access. A good scanner + manual testing covers most needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't Ignore It
&lt;/h3&gt;

&lt;p&gt;"We'll deal with it later" is how businesses get demand letters. The basics take a few hours to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  What TO Do
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Scan Your Sites
&lt;/h3&gt;

&lt;p&gt;Run an automated scan to find the low-hanging fruit. Tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://accessiguard.app" rel="noopener noreferrer"&gt;AccessiGuard&lt;/a&gt; — free scan, 22 WCAG checks (full disclosure: I built this)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://wave.webaim.org/" rel="noopener noreferrer"&gt;WAVE&lt;/a&gt; — browser extension&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.deque.com/axe/devtools/" rel="noopener noreferrer"&gt;axe DevTools&lt;/a&gt; — Chrome extension&lt;/li&gt;
&lt;li&gt;Lighthouse (built into Chrome DevTools)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single tool catches everything. Use 2-3.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fix the Automated Issues
&lt;/h3&gt;

&lt;p&gt;Alt text, labels, headings, lang attribute — these are mechanical fixes. A developer can knock out 80% of automated issues in a day.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Manual Testing
&lt;/h3&gt;

&lt;p&gt;Automated tools catch ~30-40% of issues. For the rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard test:&lt;/strong&gt; Tab through your entire site without a mouse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen reader test:&lt;/strong&gt; Try NVDA (Windows) or VoiceOver (Mac) on key pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zoom test:&lt;/strong&gt; Set browser to 200% zoom and check layout&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Set Up Monitoring
&lt;/h3&gt;

&lt;p&gt;Accessibility is ongoing. Sites change. New content gets added. A monthly scan catches regressions before they become problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Business Case (For When Clients Push Back)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Legal risk:&lt;/strong&gt; Average ADA settlement is $5,000-25,000. Annual scanning costs $348.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market size:&lt;/strong&gt; 26% of US adults have a disability (CDC). That's customers you're excluding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO benefit:&lt;/strong&gt; Many accessibility improvements (alt text, headings, semantic HTML) directly improve SEO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;April 24 creates urgency:&lt;/strong&gt; Even if the rule doesn't directly apply, the attention spike increases lawsuit risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;April 24, 2026 deadline is for government entities, but affects everyone indirectly&lt;/li&gt;
&lt;li&gt;WCAG 2.1 AA is the standard — fix alt text, labels, contrast, keyboard nav, headings&lt;/li&gt;
&lt;li&gt;Don't buy widgets. Do fix your code.&lt;/li&gt;
&lt;li&gt;Scan → fix → test manually → monitor&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>news</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Do a Website Accessibility Audit in 2026: A Step-by-Step Guide</title>
      <dc:creator>Zdenek Spacek</dc:creator>
      <pubDate>Fri, 13 Feb 2026 08:46:24 +0000</pubDate>
      <link>https://forem.com/primestark/how-to-do-a-website-accessibility-audit-in-2026-a-step-by-step-guide-n03</link>
      <guid>https://forem.com/primestark/how-to-do-a-website-accessibility-audit-in-2026-a-step-by-step-guide-n03</guid>
      <description>&lt;p&gt;Whether you're preparing for the April 2026 ADA deadline, the European Accessibility Act, or simply want to make your website usable by everyone — the first step is always the same: &lt;strong&gt;an accessibility audit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But where do you start? What do you check? And how do you prioritize what to fix first?&lt;/p&gt;

&lt;p&gt;This guide walks you through a complete website accessibility audit, step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Website Accessibility Audit?
&lt;/h2&gt;

&lt;p&gt;An accessibility audit is a systematic review of your website against established standards — typically &lt;strong&gt;WCAG 2.1 Level AA&lt;/strong&gt; — to identify barriers that prevent people with disabilities from using your site.&lt;/p&gt;

&lt;p&gt;A thorough audit combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated scanning&lt;/strong&gt; — tools that crawl your pages and flag code-level issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual testing&lt;/strong&gt; — human review of keyboard navigation, screen reader compatibility, and user experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assistive technology testing&lt;/strong&gt; — verifying your site works with screen readers, voice control, and other tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single method catches everything. Automated tools find roughly 30-50% of WCAG issues. The rest requires human judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Define Your Scope
&lt;/h2&gt;

&lt;p&gt;Before scanning anything, decide what you're auditing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full site or key pages?&lt;/strong&gt; For large sites, start with your most critical user journeys: homepage, navigation, signup/login, checkout, contact forms, and your top 10 most-visited pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which standard?&lt;/strong&gt; WCAG 2.1 Level AA is the legal benchmark in most jurisdictions. Level AAA is aspirational but rarely required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile too?&lt;/strong&gt; Responsive design issues often create accessibility barriers. Test at multiple breakpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Document your scope. You'll need it for compliance records and to track progress over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Run an Automated Scan
&lt;/h2&gt;

&lt;p&gt;Start with automated tools to get a baseline. This catches the "low-hanging fruit" — issues that are clearly detectable in code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What automated scans catch well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing alt text on images&lt;/li&gt;
&lt;li&gt;Poor color contrast ratios&lt;/li&gt;
&lt;li&gt;Missing form labels&lt;/li&gt;
&lt;li&gt;Incorrect heading hierarchy&lt;/li&gt;
&lt;li&gt;Missing document language&lt;/li&gt;
&lt;li&gt;ARIA attribute errors&lt;/li&gt;
&lt;li&gt;Empty links and buttons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What they miss:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether alt text is actually &lt;em&gt;meaningful&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Keyboard trap issues in complex widgets&lt;/li&gt;
&lt;li&gt;Logical reading order&lt;/li&gt;
&lt;li&gt;Context-dependent issues (like whether a link makes sense out of context)&lt;/li&gt;
&lt;li&gt;Dynamic content and single-page app interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to scan with AccessiGuard
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://accessiguard.app" rel="noopener noreferrer"&gt;accessiguard.app&lt;/a&gt; and enter your URL&lt;/li&gt;
&lt;li&gt;Our scanner crawls your pages and tests against WCAG 2.1 criteria&lt;/li&gt;
&lt;li&gt;Review the report — issues are categorized by severity and WCAG success criterion&lt;/li&gt;
&lt;li&gt;Export or share the report with your development team&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A free scan gives you an immediate snapshot. Paid plans add multi-page scanning, monitoring, and historical tracking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Manual Keyboard Testing
&lt;/h2&gt;

&lt;p&gt;This is where you find the issues automated tools can't. Put your mouse aside and try to use your entire site with only your keyboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to test:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Tab order:&lt;/strong&gt; Press &lt;code&gt;Tab&lt;/code&gt; repeatedly. Does focus move through the page in a logical order? Can you reach every interactive element (links, buttons, form fields, menus)?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus visibility:&lt;/strong&gt; Can you always see &lt;em&gt;where&lt;/em&gt; you are on the page? A visible focus indicator (outline, highlight) should be present on every focused element.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyboard traps:&lt;/strong&gt; Can you always &lt;code&gt;Tab&lt;/code&gt; &lt;em&gt;out&lt;/em&gt; of every component? Modal dialogs, dropdown menus, and embedded content (like maps or videos) are common trap points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip navigation:&lt;/strong&gt; Does the site have a "Skip to main content" link that appears when you first press &lt;code&gt;Tab&lt;/code&gt;? Without it, keyboard users must tab through the entire header on every page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interactive elements:&lt;/strong&gt; Can you open menus, submit forms, activate buttons, and close dialogs using only &lt;code&gt;Enter&lt;/code&gt;, &lt;code&gt;Space&lt;/code&gt;, &lt;code&gt;Escape&lt;/code&gt;, and arrow keys?&lt;/p&gt;

&lt;h3&gt;
  
  
  Common keyboard failures:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Custom dropdown menus that only respond to mouse clicks&lt;/li&gt;
&lt;li&gt;Modal dialogs that don't trap focus (focus escapes behind the overlay)&lt;/li&gt;
&lt;li&gt;Carousels with no keyboard controls&lt;/li&gt;
&lt;li&gt;"Click here" elements built with &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; instead of &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Screen Reader Testing
&lt;/h2&gt;

&lt;p&gt;Test with at least one screen reader to understand how assistive technology users experience your site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free screen reader options:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NVDA&lt;/strong&gt; (Windows) — free, widely used&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VoiceOver&lt;/strong&gt; (macOS/iOS) — built into Apple devices, activate with &lt;code&gt;Cmd+F5&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TalkBack&lt;/strong&gt; (Android) — built into Android devices&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What to listen for:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Images:&lt;/strong&gt; Are they described? Do decorative images get skipped?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headings:&lt;/strong&gt; Does the heading structure make sense when navigated by heading?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forms:&lt;/strong&gt; Are labels announced when you focus each field? Are error messages associated with the correct field?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Links:&lt;/strong&gt; Do link texts make sense without surrounding context?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Landmarks:&lt;/strong&gt; Can the screen reader identify page regions (header, nav, main, footer)?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic content:&lt;/strong&gt; When content updates (notifications, live regions, loading states), is the screen reader notified?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Check Color and Visual Design
&lt;/h2&gt;

&lt;p&gt;Visual accessibility goes beyond color contrast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color contrast
&lt;/h3&gt;

&lt;p&gt;Text must meet minimum contrast ratios against its background:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normal text:&lt;/strong&gt; 4.5:1 ratio (WCAG AA)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large text&lt;/strong&gt; (18px+ bold or 24px+ regular): 3:1 ratio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI components and graphics:&lt;/strong&gt; 3:1 ratio against adjacent colors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use tools like the &lt;a href="https://webaim.org/resources/contrastchecker/" rel="noopener noreferrer"&gt;WebAIM Contrast Checker&lt;/a&gt; to verify.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beyond contrast:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't rely on color alone&lt;/strong&gt; to convey information (red/green for error/success). Add icons, text, or patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text resizing:&lt;/strong&gt; Does your layout hold up at 200% zoom? Users with low vision depend on this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion:&lt;/strong&gt; Can animations and auto-playing content be paused? Some users experience vestibular disorders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 6: Test Forms and Error Handling
&lt;/h2&gt;

&lt;p&gt;Forms are where accessibility failures have the most direct impact — they block users from signing up, purchasing, or contacting you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checklist:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Every input has a visible, programmatically associated &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Required fields are indicated (not just with color)&lt;/li&gt;
&lt;li&gt;Error messages are specific ("Email format is invalid" not just "Error")&lt;/li&gt;
&lt;li&gt;Errors are associated with the correct field (via &lt;code&gt;aria-describedby&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Focus moves to the first error when a form submission fails&lt;/li&gt;
&lt;li&gt;Success confirmation is announced to screen readers&lt;/li&gt;
&lt;li&gt;Autocomplete attributes are used where appropriate (&lt;code&gt;autocomplete="email"&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Review Media Content
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Images
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Informative images have descriptive &lt;code&gt;alt&lt;/code&gt; text&lt;/li&gt;
&lt;li&gt;Decorative images have empty &lt;code&gt;alt=""&lt;/code&gt; (so screen readers skip them)&lt;/li&gt;
&lt;li&gt;Complex images (charts, infographics) have extended descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Video
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Captions are provided for all pre-recorded video&lt;/li&gt;
&lt;li&gt;Audio descriptions are available for video content where the visual information isn't conveyed through audio alone&lt;/li&gt;
&lt;li&gt;Auto-playing video can be paused&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Audio
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Transcripts are provided for audio-only content (podcasts, audio clips)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 8: Prioritize and Fix
&lt;/h2&gt;

&lt;p&gt;You'll likely find dozens (or hundreds) of issues. Don't panic. Prioritize by impact:&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix first (Critical):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Keyboard traps — users literally can't proceed&lt;/li&gt;
&lt;li&gt;Missing form labels — forms are unusable&lt;/li&gt;
&lt;li&gt;No focus indicators — keyboard users are lost&lt;/li&gt;
&lt;li&gt;Auto-playing audio/video that can't be stopped&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fix next (High):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Missing alt text on informative images&lt;/li&gt;
&lt;li&gt;Poor color contrast on body text&lt;/li&gt;
&lt;li&gt;Heading hierarchy issues&lt;/li&gt;
&lt;li&gt;Missing skip navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fix later (Medium):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Decorative images with non-empty alt text&lt;/li&gt;
&lt;li&gt;Minor contrast issues on non-essential elements&lt;/li&gt;
&lt;li&gt;Missing autocomplete attributes&lt;/li&gt;
&lt;li&gt;Inconsistent focus styles&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 9: Document and Monitor
&lt;/h2&gt;

&lt;p&gt;An audit isn't a one-time event. Accessibility is ongoing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Save your baseline report&lt;/strong&gt; — you need to show progress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up monitoring&lt;/strong&gt; — new code deployments can introduce regressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create an accessibility statement&lt;/strong&gt; — publish your commitment and contact info for feedback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule regular audits&lt;/strong&gt; — quarterly automated scans plus semi-annual manual reviews is a reasonable cadence&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Long Does an Audit Take?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site Size&lt;/th&gt;
&lt;th&gt;Automated Scan&lt;/th&gt;
&lt;th&gt;Manual Review&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small (5-10 pages)&lt;/td&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;td&gt;2-4 hours&lt;/td&gt;
&lt;td&gt;Half a day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium (50-100 pages)&lt;/td&gt;
&lt;td&gt;15 minutes&lt;/td&gt;
&lt;td&gt;1-2 days&lt;/td&gt;
&lt;td&gt;2-3 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large (500+ pages)&lt;/td&gt;
&lt;td&gt;30-60 minutes&lt;/td&gt;
&lt;td&gt;1-2 weeks&lt;/td&gt;
&lt;td&gt;2-3 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Automated scanning is fast. The manual review is where the real time goes — and where the most impactful issues are found.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Your Audit Now
&lt;/h2&gt;

&lt;p&gt;The best time to audit your website was before you launched. The second best time is now.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://accessiguard.app" rel="noopener noreferrer"&gt;Run a free scan&lt;/a&gt;&lt;/strong&gt; to get your baseline&lt;/li&gt;
&lt;li&gt;Follow this guide for manual testing&lt;/li&gt;
&lt;li&gt;Prioritize fixes by impact&lt;/li&gt;
&lt;li&gt;Set up monitoring to catch regressions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the ADA deadline approaching and accessibility lawsuits surging, proactive auditing isn't just good practice — it's risk management.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Need help interpreting your scan results? Reach out at &lt;a href="mailto:support@accessiguard.app"&gt;support@accessiguard.app&lt;/a&gt; — happy to help.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>a11y</category>
    </item>
    <item>
      <title>I Built a Free WCAG Accessibility Scanner — Here's What I Learned</title>
      <dc:creator>Zdenek Spacek</dc:creator>
      <pubDate>Thu, 12 Feb 2026 12:59:57 +0000</pubDate>
      <link>https://forem.com/primestark/i-built-a-free-wcag-accessibility-scanner-heres-what-i-learned-e28</link>
      <guid>https://forem.com/primestark/i-built-a-free-wcag-accessibility-scanner-heres-what-i-learned-e28</guid>
      <description>&lt;p&gt;As a solo developer building in public, I recently launched &lt;a href="https://accessiguard.app" rel="noopener noreferrer"&gt;AccessiGuard&lt;/a&gt; — a free WCAG accessibility scanner. What started as a side project to help developers catch accessibility issues early has taught me more about web standards, automated testing, and edge cases than I ever expected.&lt;/p&gt;

&lt;p&gt;Here's the technical journey, the challenges I faced, and what I learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Another Accessibility Tool?
&lt;/h2&gt;

&lt;p&gt;The accessibility landscape is changing fast. The EU's European Accessibility Act (EAA) is already in effect, and US government entities face an April 2026 deadline for WCAG compliance. Just last year, accessiBe was fined $1 million by the FTC for misleading accessibility claims.&lt;/p&gt;

&lt;p&gt;I wanted to build something honest: a tool that tells you what it &lt;em&gt;actually&lt;/em&gt; checks, doesn't make inflated promises, and remains free for developers who want to catch issues before they become lawsuits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;I kept it intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 14&lt;/strong&gt; (App Router) — for the web interface and API routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cheerio&lt;/strong&gt; — for fast, server-side HTML parsing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; — because accessibility checks require precision&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt; — for the frontend dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; — for hosting and edge functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The beauty of Cheerio over a full browser automation tool like Puppeteer is speed. We can parse and analyze HTML in milliseconds rather than seconds. The tradeoff? We can't check everything that requires JavaScript execution or visual rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Actually Check
&lt;/h2&gt;

&lt;p&gt;I'll be honest: automated tools can only catch about &lt;strong&gt;30-40%&lt;/strong&gt; of WCAG issues. The rest require human judgment. But that 30-40% matters — those are the low-hanging fruit that plague most websites.&lt;/p&gt;

&lt;p&gt;Here's what AccessiGuard scans for:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Missing Alt Text on Images
&lt;/h3&gt;

&lt;p&gt;This is the most common issue. Here's a simplified version of the check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkImageAlt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;img&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Decorative images should have empty alt or role="presentation"&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;presentation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Skip&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Non-decorative images must have alt text&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing-alt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;wcag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.1.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Challenge faced:&lt;/strong&gt; Distinguishing between truly missing alt attributes and intentionally empty ones (&lt;code&gt;alt=""&lt;/code&gt;). Empty alt is valid for decorative images, but missing alt is always an error.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Form Labels
&lt;/h3&gt;

&lt;p&gt;Forms are critical for accessibility. Every input needs a label:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkFormLabels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input, select, textarea&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ariaLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ariaLabelledby&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-labelledby&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Skip hidden inputs and buttons&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Check for label association&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`label[for="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasAriaLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ariaLabel&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ariaLabelledby&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasLabel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasAriaLabel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing-form-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;wcag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3.3.2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Challenge faced:&lt;/strong&gt; Modern frameworks like React often use &lt;code&gt;aria-label&lt;/code&gt; or wrap inputs in labels without &lt;code&gt;for&lt;/code&gt; attributes. I had to account for multiple valid labeling patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Heading Hierarchy
&lt;/h3&gt;

&lt;p&gt;Headings should follow a logical order (h1 → h2 → h3), not skip levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkHeadingOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1, h2, h3, h4, h5, h6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if we skip levels (e.g., h2 → h4)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading-skip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Heading level &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; appears after level &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;wcag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.3.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Challenge faced:&lt;/strong&gt; Some modern designs intentionally use CSS to style headings differently than their semantic level. I had to decide whether to flag semantic issues or trust the developer's intent.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Color Contrast
&lt;/h3&gt;

&lt;p&gt;This is where it gets tricky. Without rendering the page, we can't truly measure contrast. So AccessiGuard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parses inline styles and &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags&lt;/li&gt;
&lt;li&gt;Flags suspicious color combinations&lt;/li&gt;
&lt;li&gt;Recommends manual testing with browser DevTools
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkColorContrast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;warnings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[style*="color"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$elem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Simple regex to extract colors (not production-ready)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colorMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/color:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bgMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/background&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;-color&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colorMatch&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;bgMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contrast-warning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Manual contrast check recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$elem&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;wcag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.4.3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Challenge faced:&lt;/strong&gt; Accurate contrast calculation requires computed styles from a rendered page. I chose to flag potential issues and recommend tools like WAVE or Axe DevTools for final verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Language Declaration
&lt;/h3&gt;

&lt;p&gt;Simple but critical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;htmlLang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lang&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;htmlLang&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing-lang&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HTML element missing lang attribute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;wcag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3.1.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Here's how a scan works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User submits URL&lt;/strong&gt; via the Next.js frontend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API route&lt;/strong&gt; (&lt;code&gt;/api/scan&lt;/code&gt;) receives the request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch HTML&lt;/strong&gt; using native &lt;code&gt;fetch()&lt;/code&gt; with a 10-second timeout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse with Cheerio&lt;/strong&gt; — convert HTML string to queryable DOM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run checks&lt;/strong&gt; — all check functions execute in parallel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate results&lt;/strong&gt; — combine issues by severity (A, AA, AAA)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return JSON&lt;/strong&gt; — frontend displays results&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire scan typically takes &lt;strong&gt;500ms to 2 seconds&lt;/strong&gt;, depending on page size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge Cases and Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  SVGs with &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; elements
&lt;/h3&gt;

&lt;p&gt;Screen readers handle SVG accessibility differently across browsers. I initially flagged SVGs without &lt;code&gt;aria-label&lt;/code&gt;, but missed that &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; elements inside SVGs are valid accessible names.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Content
&lt;/h3&gt;

&lt;p&gt;Single-page apps (SPAs) often render content client-side. Cheerio only sees the initial HTML. Solution: I added a notice recommending browser-based tools (Axe DevTools, Lighthouse) for SPA testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iframe Content
&lt;/h3&gt;

&lt;p&gt;Iframes are separate documents. I can detect their presence but can't scan cross-origin content without violating CORS. I flag this limitation in the report.&lt;/p&gt;

&lt;h3&gt;
  
  
  ARIA Overrides
&lt;/h3&gt;

&lt;p&gt;If an element has &lt;code&gt;aria-hidden="true"&lt;/code&gt;, it's invisible to screen readers — even if it has other accessibility issues. I had to adjust checks to respect ARIA states.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use a headless browser for premium scans.&lt;/strong&gt; Cheerio is fast but limited. For a paid tier, I'd add Playwright or Puppeteer to check rendered styles, computed contrast, and JavaScript-generated content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add axe-core integration.&lt;/strong&gt; The &lt;a href="https://github.com/dequelabs/axe-core" rel="noopener noreferrer"&gt;axe-core&lt;/a&gt; library is battle-tested and catches issues I haven't coded for yet. I wanted to build the core myself first to learn, but I'll likely integrate it soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More granular reporting.&lt;/strong&gt; Right now, results are grouped by WCAG level. I should add filtering by issue type, element, and page section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility is hard.&lt;/strong&gt; Even automated checks require nuance. There's no one-size-fits-all rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be honest about limitations.&lt;/strong&gt; Users trust tools that admit what they &lt;em&gt;can't&lt;/em&gt; do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed matters.&lt;/strong&gt; Developers won't use a slow tool. Cheerio's simplicity pays off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge cases are infinite.&lt;/strong&gt; Every new scan reveals a pattern I didn't anticipate.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Already Live
&lt;/h2&gt;

&lt;p&gt;Since the initial build, I've shipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Continuous monitoring&lt;/strong&gt; — scheduled scans with email alerts (paid tiers at $29/$79/$199/month)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical tracking&lt;/strong&gt; — see how your accessibility score changes over time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-page scans&lt;/strong&gt; — crawl entire sites, not just one page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-powered fix suggestions&lt;/strong&gt; — actionable code snippets to resolve each issue&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;What I'm working on now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PDF compliance reports&lt;/strong&gt; — downloadable, shareable with clients or legal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD integration&lt;/strong&gt; — GitHub Action to catch accessibility regressions before deploy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EU localization&lt;/strong&gt; — Czech/German landing pages for the European market&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;AccessiGuard is free and always will be for single scans. No signup required.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://accessiguard.app" rel="noopener noreferrer"&gt;accessiguard.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scan your site, get actionable feedback, and fix issues before they become problems.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Building in public as a solo founder.&lt;/strong&gt; If you have feedback, questions, or want to discuss accessibility testing, drop a comment below. I read and respond to everything.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want more technical deep-dives? Follow me here on Dev.to. Next up: "How I Built Continuous Accessibility Monitoring with Cron Jobs and Serverless Functions."&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
