wcag-a11y 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ const EXPLANATIONS = {
2
+ // Text alternatives
3
+ 'img-alt': 'Screen reader users hear nothing for this image — any information it conveys (branding, instructions, data) is completely invisible to them.',
4
+ 'input-image-alt': 'Screen readers announce this button by its file name (e.g. "submit.png") instead of its purpose, so users cannot tell what the button does.',
5
+ 'svg-title': 'Screen reader users receive no label for this SVG; if it conveys meaning (an icon, a chart), that meaning is entirely lost.',
6
+ 'object-alt': 'Screen reader users receive no description for this embedded object; its content is skipped as if it does not exist.',
7
+ 'role-img-alt': 'Elements marked with `role="img"` are announced as "image" with no label, giving screen reader users no context about what is depicted.',
8
+ 'image-redundant-alt': 'Screen reader users hear the same text twice — once from the alt attribute and once from adjacent text — creating a disorienting, repetitive experience.',
9
+ // Tables
10
+ 'table-headers': 'Screen reader users navigating a data table cell by cell hear only isolated values with no column or row context — like reading a spreadsheet with all headers removed.',
11
+ 'table-scope-valid': 'Invalid `scope` values cause screen readers to misidentify which headers apply to which cells, giving users incorrect data relationships.',
12
+ 'td-headers-attr': '`headers` attributes pointing to non-existent IDs are silently ignored, leaving table cells without any header association for screen reader users.',
13
+ 'table-duplicate-name': 'When a table\'s caption and summary say the same thing, screen reader users hear the description twice, wasting time and causing confusion.',
14
+ // Structure
15
+ 'heading-order': 'Skipping heading levels (e.g. h1 → h3) breaks the document outline; screen reader users navigating by headings lose the logical hierarchy and cannot tell which sections are sub-sections of others.',
16
+ 'page-title': 'Screen reader users hear a blank or generic title when switching tabs and cannot identify the page without reading its full content.',
17
+ 'landmark-one-main': 'Without a `<main>` landmark, screen reader users cannot skip directly to page content and must tab through all navigation on every single page load.',
18
+ 'list-structure': 'Content that looks like a list but uses `<div>` or `<p>` instead of `<ul>`/`<li>` is read as plain text; screen readers won\'t announce item count or allow list-navigation shortcuts.',
19
+ 'region-landmark': 'Content outside any landmark region is missed by screen reader users who navigate by landmarks — `<header>`, `<main>`, `<nav>`, `<footer>` etc.',
20
+ 'duplicate-id': 'Duplicate IDs silently break ARIA relationships (`aria-labelledby`, `aria-describedby`): the wrong element may be read, or the association ignored entirely.',
21
+ 'frame-title': 'Screen reader users hear "frame" with no description and cannot tell if a frame contains a cookie banner, an advertisement, or a critical widget without entering it.',
22
+ 'meta-viewport': '`user-scalable=no` or `maximum-scale` prevents users with low vision from pinch-zooming — removing the browser\'s most basic accessibility tool for text size.',
23
+ 'marquee': '`<marquee>` moves content automatically with no pause control; users with cognitive disabilities, ADHD, or vestibular disorders find it disorienting or impossible to read.',
24
+ 'p-as-heading': 'Visual headings styled as bold `<p>` tags are not exposed as headings in the accessibility tree; screen reader users navigating by headings will skip this section entirely.',
25
+ // Media
26
+ 'video-captions': 'Deaf and hard-of-hearing users cannot access any spoken content in the video — dialogue, narration, and audio cues are entirely unavailable to them.',
27
+ 'audio-description': 'Blind users miss visual-only information in the video (on-screen text, actions, scene changes) that is never described in the audio track.',
28
+ 'audio-transcript': 'Deaf users cannot access audio-only content (e.g. podcasts, voice instructions) without a text transcript; the content is completely inaccessible to them.',
29
+ // Links
30
+ 'link-name': 'Screen reader users navigating by links hear only "link" with no destination — they cannot determine where it leads without reading the surrounding paragraph.',
31
+ 'link-empty': 'An anchor with no text content is announced as an empty unlabeled link; screen reader users encounter a dead spot that provides no usable information.',
32
+ 'identical-links-different-purpose': 'Multiple links that read the same (e.g. "Read more") but go to different destinations give screen reader users no way to distinguish them when browsing the links list.',
33
+ 'link-new-window-warn': 'Links that silently open new tabs disorient keyboard and screen reader users — the Back button no longer works and the unexpected context shift is never announced.',
34
+ // Forms
35
+ 'label-missing': 'Screen reader users tab to this field and hear only the input type (e.g. "text, edit"); they receive no indication of what information the field expects.',
36
+ 'label-empty': 'The `<label>` exists but contains no text; screen reader users hear the input type only, with no indication of the field\'s purpose.',
37
+ 'error-identification': 'Form validation errors are conveyed visually only; screen reader users receive no programmatic notification and cannot identify which fields failed or what went wrong.',
38
+ 'autocomplete': 'Without the correct `autocomplete` attribute, password managers and browser autofill cannot identify the field, forcing users with motor disabilities to type credentials manually every time.',
39
+ 'input-button-name': 'Screen reader users hear "button" with no label and cannot determine what action the button performs.',
40
+ 'fieldset-legend': 'Related form controls have no group label; screen reader users navigating into the group have no context for what the inputs belong to (e.g. "Is this billing or shipping?").',
41
+ 'form-field-required-label': 'Required fields are not programmatically marked as required; screen reader users are not warned the field is mandatory and may submit an incomplete form unexpectedly.',
42
+ // Language
43
+ 'html-lang': 'Without a `lang` attribute, screen readers use the system language to read all content; foreign-language text is mispronounced and can be incomprehensible.',
44
+ 'html-lang-valid': 'An unrecognized `lang` value causes screen readers to fall back to the system language, mispronouncing content and breaking language-specific text-to-speech features.',
45
+ // Color contrast
46
+ 'color-contrast-text': 'Users with low vision or color blindness cannot distinguish this text from its background at the current contrast ratio; the text becomes difficult or impossible to read without assistive magnification.',
47
+ 'color-contrast-large-text': 'Despite large text having a more relaxed contrast requirement, this element still fails that threshold; users with moderate low vision cannot read it reliably.',
48
+ // Keyboard
49
+ 'no-positive-tabindex': 'Positive `tabindex` values override the natural tab order, causing keyboard users to jump erratically around the page instead of following its logical structure.',
50
+ 'interactive-not-focusable': 'Keyboard users and screen reader users cannot reach this interactive element by tabbing — it is effectively invisible and unusable to anyone not using a mouse.',
51
+ 'skip-link': 'Without a "skip to main content" link, keyboard users must tab through every navigation item on every page load before reaching the main content — often 20–40 tab presses on complex sites.',
52
+ 'focus-visible': 'Keyboard users cannot see which element currently has focus; they lose their place on the page and cannot tell where their next keypress will act.',
53
+ 'scrollable-region-focusable': 'A scrollable area that cannot receive keyboard focus traps keyboard users — they can see there is more content but cannot scroll to it without a mouse.',
54
+ 'accesskey-unique': 'Duplicate `accesskey` values cause browsers to cycle through elements unpredictably; keyboard shortcut users cannot rely on the shortcut to reach the intended element.',
55
+ // ARIA
56
+ 'aria-valid-role': 'An unrecognized ARIA role is ignored by assistive technologies; the element is presented without semantic meaning and screen reader users may navigate past it or interact with it incorrectly.',
57
+ 'aria-required-attr': 'This ARIA widget is missing required attributes (e.g. `aria-checked` on `role="checkbox"`); screen readers cannot communicate the element\'s state, so users don\'t know if it is active, checked, or selected.',
58
+ 'aria-hidden-focus': 'A focusable element inside `aria-hidden` receives keyboard focus but is invisible to screen readers; keyboard users land on an element and the screen reader announces nothing.',
59
+ 'button-name': 'Screen reader users hear "button" with no label and cannot determine what action the button performs without exploring surrounding content visually.',
60
+ 'aria-required-children': 'This ARIA container is missing required child roles (e.g. a `listbox` with no `option` children); screen readers either skip the widget or announce it in an unpredictable way.',
61
+ 'aria-required-parent': 'This ARIA element is outside its required parent context; screen readers cannot establish the correct ownership relationship and may misreport the element\'s role or state.',
62
+ 'aria-prohibited-attr': 'ARIA attributes applied where they are prohibited override the element\'s built-in semantics, causing screen readers to misannounce the element\'s role or state.',
63
+ };
64
+ export function fallbackExplanation(ruleId, description, wcag, level) {
65
+ return EXPLANATIONS[ruleId]
66
+ ?? `${description} — this prevents some users from accessing or understanding content on this page (WCAG 2.1 SC ${wcag}, Level ${level}).`;
67
+ }
package/dist/ai/gemini.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { buildPrompt } from './prompt.js';
2
2
  import { groupViolations } from './group.js';
3
+ import { fallbackExplanation } from './fallback-explanation.js';
3
4
  export class GeminiProvider {
4
5
  apiKey;
5
6
  model;
@@ -46,7 +47,7 @@ export class GeminiProvider {
46
47
  fallbackFix(g) {
47
48
  const v = g.representative;
48
49
  const selectorList = g.selectors.map((s) => `- ${s}`).join('\n');
49
- const explanation = `Users relying on assistive technologies are affected: ${g.description.toLowerCase()}. This fails WCAG 2.1 SC ${g.wcag} (Level ${g.level}).`;
50
+ const explanation = fallbackExplanation(g.ruleId, g.description, g.wcag, g.level);
50
51
  const prompt = g.count > 1
51
52
  ? `Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected elements (${g.count} instances):\n${selectorList}\n\nRepresentative HTML:\n\`${v.html.slice(0, 300)}\`\n\nApply the fix to all ${g.count} instances in the codebase to comply with WCAG 2.1 SC ${g.wcag}.`
52
53
  : `Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected element:\n- Selector: \`${g.selectors[0]}\`\n- HTML: \`${v.html.slice(0, 300)}\`\n\nApply the fix to comply with WCAG 2.1 SC ${g.wcag}.`;
package/dist/ai/ollama.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { buildPrompt } from './prompt.js';
2
2
  import { groupViolations } from './group.js';
3
+ import { fallbackExplanation } from './fallback-explanation.js';
3
4
  export class OllamaProvider {
4
5
  baseUrl;
5
6
  model;
@@ -44,7 +45,7 @@ export class OllamaProvider {
44
45
  fallbackFix(g) {
45
46
  const v = g.representative;
46
47
  const selectorList = g.selectors.map((s) => `- ${s}`).join('\n');
47
- const explanation = `Users relying on assistive technologies are affected: ${g.description.toLowerCase()}. This fails WCAG 2.1 SC ${g.wcag} (Level ${g.level}).`;
48
+ const explanation = fallbackExplanation(g.ruleId, g.description, g.wcag, g.level);
48
49
  const prompt = g.count > 1
49
50
  ? `Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected elements (${g.count} instances):\n${selectorList}\n\nRepresentative HTML:\n\`${v.html.slice(0, 300)}\`\n\nApply the fix to all ${g.count} instances in the codebase to comply with WCAG 2.1 SC ${g.wcag}.`
50
51
  : `Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected element:\n- Selector: \`${g.selectors[0]}\`\n- HTML: \`${v.html.slice(0, 300)}\`\n\nApply the fix to comply with WCAG 2.1 SC ${g.wcag}.`;
package/dist/ai/openai.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { buildPrompt } from './prompt.js';
2
2
  import { groupViolations } from './group.js';
3
+ import { fallbackExplanation } from './fallback-explanation.js';
3
4
  export class OpenAIProvider {
4
5
  apiKey;
5
6
  model;
@@ -50,7 +51,7 @@ export class OpenAIProvider {
50
51
  fallbackFix(g) {
51
52
  const v = g.representative;
52
53
  const selectorList = g.selectors.map((s) => `- ${s}`).join('\n');
53
- const explanation = `Users relying on assistive technologies are affected: ${g.description.toLowerCase()}. This fails WCAG 2.1 SC ${g.wcag} (Level ${g.level}).`;
54
+ const explanation = fallbackExplanation(g.ruleId, g.description, g.wcag, g.level);
54
55
  const prompt = g.count > 1
55
56
  ? `Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected elements (${g.count} instances):\n${selectorList}\n\nRepresentative HTML:\n\`${v.html.slice(0, 300)}\`\n\nApply the fix to all ${g.count} instances in the codebase to comply with WCAG 2.1 SC ${g.wcag}.`
56
57
  : `Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected element:\n- Selector: \`${g.selectors[0]}\`\n- HTML: \`${v.html.slice(0, 300)}\`\n\nApply the fix to comply with WCAG 2.1 SC ${g.wcag}.`;
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ const program = new Command();
11
11
  program
12
12
  .name('wcag-a11y')
13
13
  .description('WCAG 2.1/2.2 accessibility auditor with AI-powered fixes')
14
- .version('0.3.0');
14
+ .version('0.3.2');
15
15
  program
16
16
  .command('init')
17
17
  .description('Create a11y.config.json in the current directory')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wcag-a11y",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "WCAG 2.1/2.2 accessibility auditor with AI-powered fixes",
5
5
  "type": "module",
6
6
  "bin": {