From 6e308d29825c423293cfeafc1f3f12004033269b Mon Sep 17 00:00:00 2001 From: vladyslav tertyshnyi Date: Thu, 28 May 2026 10:05:07 +0300 Subject: [PATCH] feat(wix-app): integrate a11y review into Editor React Component flow --- .../references/EDITOR_REACT_COMPONENT.md | 6 +- .../editor-react-component/A11Y-REVIEW.md | 183 ++++++ .../a11y-review/CONFIDENCE-MODEL.md | 81 +++ .../a11y-review/EXAMPLES.md | 224 +++++++ .../a11y-review/FIX-STRATEGIES.md | 206 ++++++ .../a11y-review/MANUAL-REVIEW-CHECKLIST.md | 53 ++ .../a11y-review/RULES.md | 127 ++++ .../a11y-review/SEMANTIC-RESOLUTION.md | 32 + skills/wix-app/scripts/scan-a11y-code.js | 594 ++++++++++++++++++ skills/wix-app/scripts/scan-a11y-eslint.js | 149 +++++ 10 files changed, 1653 insertions(+), 2 deletions(-) create mode 100644 skills/wix-app/references/editor-react-component/A11Y-REVIEW.md create mode 100644 skills/wix-app/references/editor-react-component/a11y-review/CONFIDENCE-MODEL.md create mode 100644 skills/wix-app/references/editor-react-component/a11y-review/EXAMPLES.md create mode 100644 skills/wix-app/references/editor-react-component/a11y-review/FIX-STRATEGIES.md create mode 100644 skills/wix-app/references/editor-react-component/a11y-review/MANUAL-REVIEW-CHECKLIST.md create mode 100644 skills/wix-app/references/editor-react-component/a11y-review/RULES.md create mode 100644 skills/wix-app/references/editor-react-component/a11y-review/SEMANTIC-RESOLUTION.md create mode 100644 skills/wix-app/scripts/scan-a11y-code.js create mode 100644 skills/wix-app/scripts/scan-a11y-eslint.js diff --git a/skills/wix-app/references/EDITOR_REACT_COMPONENT.md b/skills/wix-app/references/EDITOR_REACT_COMPONENT.md index f641fae2..f4bf67cf 100644 --- a/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +++ b/skills/wix-app/references/EDITOR_REACT_COMPONENT.md @@ -54,10 +54,11 @@ File where you can override the generated manifest from `.generat `[[ -d "node_modules/@wix/react-component-schema" && -d "node_modules/@wix/react-component-utils" && -d "node_modules/@wix/editor-react-types" ]] || ([ -f yarn.lock ] && yarn add @wix/react-component-schema @wix/react-component-utils @wix/editor-react-types || npm install @wix/react-component-schema @wix/react-component-utils @wix/editor-react-types)` 3. Edit the generated react and CSS files in `src/site/components/ComponentName/`. -4. Run `npx wix build && npx wix generate manifest` so the editor picks up +4. Run the A11y Review on the edited component per [`editor-react-component/A11Y-REVIEW.md`](editor-react-component/A11Y-REVIEW.md). It runs two scanners over the component's `.tsx`/`.jsx` files, triages findings, and applies fixes before the manifest is regenerated. +5. Run `npx wix build && npx wix generate manifest` so the editor picks up the new/updated prop schema. This command regenerates manifest parts for all components. -5. Update `Component.extensions.ts` file according to [`editor-react-component/COMPONENT-CONFIGURATION.md`](editor-react-component/COMPONENT-CONFIGURATION.md) +6. Update `Component.extensions.ts` file according to [`editor-react-component/COMPONENT-CONFIGURATION.md`](editor-react-component/COMPONENT-CONFIGURATION.md) Reference: when modifying an _existing_ component, follow [`editor-react-component/EDIT-FLOW.md`](editor-react-component/EDIT-FLOW.md). @@ -69,6 +70,7 @@ Core rules and workflow: [`editor-react-component/REACT-GUIDELINES.md`](editor-r Topic-focused references (rules + patterns + common mistakes in one place): - [`editor-react-component/ACCESSIBILITY.md`](editor-react-component/ACCESSIBILITY.md) — ARIA/a11y rules and patterns +- [`editor-react-component/A11Y-REVIEW.md`](editor-react-component/A11Y-REVIEW.md) — Automated a11y scan + triage workflow to run after editing - [`editor-react-component/DIRECTIONALITY.md`](editor-react-component/DIRECTIONALITY.md) — RTL/LTR rules and patterns - [`editor-react-component/PROPS-VS-CSS.md`](editor-react-component/PROPS-VS-CSS.md) — What should be a React prop vs CSS - [`editor-react-component/COMPONENT-API.md`](editor-react-component/COMPONENT-API.md) — Props structure, elementProps, data types, file splitting, containers, array props diff --git a/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md b/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md new file mode 100644 index 00000000..c945e22e --- /dev/null +++ b/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md @@ -0,0 +1,183 @@ +# Editor React Component A11y Review + +Use this reference to audit and fix accessibility issues in an Editor React Component before reporting completion. This complements [`ACCESSIBILITY.md`](ACCESSIBILITY.md) (which covers ARIA prop conventions and patterns) by adding an automated scan + triage workflow over the component's runtime, manifest extension, and shared code. + +This is not a separate skill — it runs as part of the Editor React Component workflow described in [`../EDITOR_REACT_COMPONENT.md`](../EDITOR_REACT_COMPONENT.md). Run it after editing the React/CSS sources and before `npx wix build && npx wix generate manifest`. + +## Scanner Invocation + +Two scanners live at the wix-app skill root: + +```bash +node skills/wix-app/scripts/scan-a11y-eslint.js [file2] ... +node skills/wix-app/scripts/scan-a11y-code.js [file2] ... +``` + +Run them from the consumer project root (the Wix CLI app's working directory) so dependencies resolve from the project's `package.json`. Pass paths relative to that cwd. + +The ESLint scanner uses `eslint-plugin-jsx-a11y` (usually transitively available); the custom scanner performs cross-file semantic resolution (follows imports up to 4 levels deep) with confidence scoring. + +## When To Run + +Run the review when: + +- You finished editing an Editor React Component's `.tsx` / `.module.css` files. +- The user asks for an a11y audit, accessibility review, or a11y fix on an Editor React Component. +- The user mentions missing alt text, invalid ARIA, broken semantics, direction issues, icon-only controls, or wrappers that hide accessibility issues. + +Do not run on generated files (`*.generated.ts`) — they're regenerated from JSX and CSS Modules. + +## Target Resolution + +Resolve scope before scanning: + +| User says | Required scope | +|-----------|----------------| +| Specific file | That file plus any imported wrappers / shared components it renders | +| "this component" | `.tsx`, `component.tsx`, `.extension.ts`, plus shared imports | +| A component name | All in-component files (excluding `*.generated.ts`) | +| "full audit" | Every Editor React Component folder under `src/site/components/`, excluding `*.generated.ts` | + +## Workflow + +Execute phases in order; do not pause for approval between phases. + +### Phase 1: Resolve Topology + +1. Enumerate in-scope files under `src/site/components//`. +2. Include any imported shared components or utilities that affect rendered semantics. +3. Exclude `*.generated.ts`. +4. Note the topology (wrapper → shared primitive → leaf element) before scanning. + +### Phase 2a: ESLint JSX A11y Scan (Tier 0) + +```bash +node skills/wix-app/scripts/scan-a11y-eslint.js [file2] ... +``` + +Single-file lint-level detections covering 31 `jsx-a11y` rules (interaction, focus, labeling, structural). High-signal for native elements; treat as leads for custom components. Full rule list: [`a11y-review/RULES.md`](a11y-review/RULES.md). + +### Phase 2b: Custom Semantic Scan (Tier 1) + +```bash +node skills/wix-app/scripts/scan-a11y-code.js [file2] ... +``` + +Cross-file semantic resolution with confidence scoring. Covers five rule families: + +- `alt-text` +- `anchor-is-valid` +- `aria-props` +- `aria-role` +- `aria-unsupported-elements` + +Neither scanner validates manifest contracts or all project-specific patterns — see Phase 3. + +### Deduplication and Triage + +After both scans, evaluate every Tier 0 finding before fixing. + +**Deduplicate.** When both scanners flag the same location for the same issue, keep the richer one — prefer the custom scanner when it has semantic evidence; prefer ESLint when it covers a rule the custom scanner does not. + +**Verdicts:** + +| Verdict | Meaning | Action | +|---------|---------|--------| +| `confirmed` | The violation is real | Proceed to fix | +| `false-positive` | Lint rule fires but code is correct | Discard with one-sentence reason | +| `not-relevant` | Rule does not apply in this architecture | Discard with one-sentence reason | + +**How to evaluate:** + +1. **Native vs custom.** Native element findings are usually real. Custom component findings need verification — does the component render the element the rule assumes? +2. **Delegated semantics.** Editor React Components route ARIA through the typed `a11y` prop and `convertA11yKeysToHtmlFormat(a11y)` (see [`ACCESSIBILITY.md`](ACCESSIBILITY.md)). If a11y attributes reach the element via a runtime path the linter can't see, the finding is a false positive. +3. **Conditional interactivity.** When `onClick` is spread conditionally with `role`, `tabIndex`, and `onKeyDown`, the finding is a false positive. If only `onClick` is conditional, the finding is confirmed. +4. **Repo-specific exemptions** — see the originals in [`a11y-review/MANUAL-REVIEW-CHECKLIST.md`](a11y-review/MANUAL-REVIEW-CHECKLIST.md). +5. **When uncertain, default to `confirmed`.** + +Only confirmed findings proceed to fix. Report discards in Phase 6 with reasons. + +### Phase 3: Editor React Component Semantic Review + +Perform a manual semantic review even when scanners return zero findings. + +Editor React Component–specific checks: + +- All ARIA attributes come through the typed `a11y?: A11y` prop — never individual `ariaLabel?: string`, `role?: string`, etc. (See [`ACCESSIBILITY.md`](ACCESSIBILITY.md).) +- `a11y` is forwarded to the root via `{...(a11y && convertA11yKeysToHtmlFormat(a11y))}`, or to an inner element via `elementProps..a11y` when requirements specify. +- Icon-only interactive elements (icon/emoji/image/svg with no text node) have an accessible name from `constants.ts` or from user-configurable `a11y` — never a hardcoded string literal. +- If the component supports text direction, the root applies `dir` (see [`DIRECTIONALITY.md`](DIRECTIONALITY.md)). +- `.extension.ts` overrides do not strip a11y-relevant manifest fields generated from the JSX. +- Decorative-only output is hidden with `aria-hidden` when appropriate. +- Heading/tag selection (when configurable) reaches the rendered semantic element. +- Interactive-looking non-interactive elements expose keyboard behavior and roles. + +Use the Tier 2 checklist in [`a11y-review/MANUAL-REVIEW-CHECKLIST.md`](a11y-review/MANUAL-REVIEW-CHECKLIST.md) alongside these checks. + +### Phase 4: Fix + +Apply fixes only to findings that survived triage. Confidence drives action: + +- `high`: fix immediately +- `medium`: read surrounding code, confirm, then fix +- `low`: attempt to resolve semantics; fix only if confirmed + +Confirmed safe/local fixes must be applied; only ambiguous or risky non-local changes may remain unresolved. See [`a11y-review/CONFIDENCE-MODEL.md`](a11y-review/CONFIDENCE-MODEL.md). + +**Fix principles:** + +- Preserve intended visual and runtime behavior. +- Fix the real semantic owner: component root, inner element via `elementProps`, or shared base. +- Prefer Editor React Component–native patterns: route ARIA through `a11y`, use `constants.ts` for required labels, never hardcode string literals. +- Before adding `role`, `tabIndex`, or keyboard handlers to a non-interactive element, run the pre-fix checks in [`a11y-review/FIX-STRATEGIES.md`](a11y-review/FIX-STRATEGIES.md#pre-fix-checks-for-adding-button-semantics-to-a-non-interactive-element). + +### Phase 5: Verify + +If any `.tsx` / `.jsx` file was edited: + +1. Re-run both scanners on the modified files. +2. Run `npx wix build && npx wix generate manifest` so the manifest reflects any JSX changes (this is also required by the Editor React Component workflow). +3. Run the project's TypeScript check (`npx tsc --noEmit`) — already part of the wix-app validation flow. + +Verification is mandatory after edits. Do not consider the review complete until it has run or you've explicitly reported why it could not. + +### Phase 6: Report + +Summarize: + +1. **Topology and scope** — files reviewed, layers covered. +2. **Tier 0 ESLint findings** — grouped by rule with dispositions. +3. **Tier 1 custom scanner findings** — grouped by rule with dispositions. +4. **Tier 2 semantic findings** — including manifest/shared-layer items neither scanner catches. +5. **Verification** — scanner re-runs, `npx wix build && npx wix generate manifest`, `npx tsc --noEmit`. +6. **Unresolved items** — with exact blockers. + +Every finding ends in one of: + +- `fixed` +- `skipped-ambiguous` +- `skipped-risky` +- `false-positive` (one-sentence reason) +- `not-relevant` (one-sentence reason) + +Any disposition other than `fixed` requires a one-sentence concrete blocker or reason. + +## Confidence Handling + +- `high`: fix immediately +- `medium`: confirm by reading code, then fix +- `low`: attempt to resolve semantics; fix only if confirmed; otherwise report as advisory + +Severity affects reporting priority, not whether a confirmed safe/local fix should be applied. + +See [`a11y-review/CONFIDENCE-MODEL.md`](a11y-review/CONFIDENCE-MODEL.md). + +## References + +- Rule criteria: [`a11y-review/RULES.md`](a11y-review/RULES.md) +- Fix strategies: [`a11y-review/FIX-STRATEGIES.md`](a11y-review/FIX-STRATEGIES.md) +- Tier 2 semantic checklist: [`a11y-review/MANUAL-REVIEW-CHECKLIST.md`](a11y-review/MANUAL-REVIEW-CHECKLIST.md) +- Semantic inference model: [`a11y-review/SEMANTIC-RESOLUTION.md`](a11y-review/SEMANTIC-RESOLUTION.md) +- Confidence handling: [`a11y-review/CONFIDENCE-MODEL.md`](a11y-review/CONFIDENCE-MODEL.md) +- Pattern examples: [`a11y-review/EXAMPLES.md`](a11y-review/EXAMPLES.md) +- ARIA conventions for Editor React Components: [`ACCESSIBILITY.md`](ACCESSIBILITY.md) diff --git a/skills/wix-app/references/editor-react-component/a11y-review/CONFIDENCE-MODEL.md b/skills/wix-app/references/editor-react-component/a11y-review/CONFIDENCE-MODEL.md new file mode 100644 index 00000000..08b58668 --- /dev/null +++ b/skills/wix-app/references/editor-react-component/a11y-review/CONFIDENCE-MODEL.md @@ -0,0 +1,81 @@ +# Confidence Model + +## Tier 0 (ESLint) Findings + +ESLint findings do not carry a confidence level from the scanner. During the triage step, assign confidence based on the flagged element: + +- **Native element** (`div`, `img`, `a`, `button`, etc.): treat as `high` confidence. The rule sees the actual semantic element. +- **Custom component that resolves to a known native element** (confirmed via Tier 1 scan or by reading the source): treat as `medium` confidence. +- **Custom component that cannot be resolved** or whose semantics are delegated through runtime props/spread: evaluate for `false-positive` or `not-relevant`. If neither applies, treat as `low` confidence. + +After assigning confidence, apply the same fix eligibility rules as Tier 1 findings below. + +--- + +## Tier 1 (Custom Scanner) and Tier 2 (Manual) Findings + +## High + +Use high confidence when: + +- the element is a native tag +- a local or package component clearly resolves to a native semantic element +- a polymorphic prop explicitly sets a native semantic element + +High-confidence findings can be presented as definite issues. + +**Fix eligibility**: Fix immediately. No confirmation needed. + +## Medium + +Use medium confidence when: + +- props strongly imply semantics +- a wrapper only partially resolves but still preserves likely semantics +- the component name and surrounding evidence strongly agree + +Medium-confidence findings can be presented as likely issues, but the code should still be read before applying a fix. + +**Fix eligibility**: Fix after reading the surrounding code to confirm the semantic inference is correct. If confirmed, fix without asking. If the code contradicts the inference, skip the fix and report the finding as unresolved. + +## Low + +Use low confidence when: + +- only weak heuristics support the semantic guess +- the implementation cannot be resolved +- the component might be semantic but the evidence is thin + +Low-confidence matches are not definite violations. Treat them as leads for manual review. + +**Fix eligibility**: Read the code and attempt to resolve the semantics. If the code confirms the issue, upgrade to medium or high and fix. If the semantics remain unclear, skip the fix and report the finding as advisory only. + +## Confidence vs. Actionability + +Confidence answers "is this issue real?" It does not answer "is this issue important enough to fix?" + +Use this decision rule after reading the code: + +- Confirmed + safe/local fix => fix now. +- Confirmed + risky behavior change or non-local refactor => report as unresolved with the exact blocker. +- Unconfirmed or ambiguous => advisory only. + +If you can see the violation in the code and describe a localized, behavior-preserving fix, treat it as confirmed and fix it. + +## Valid Reasons to Skip a Fix + +Only skip a fix when one of these is true: + +- the semantics remain ambiguous after reading the code +- product intent cannot be derived from the code +- the fix would require a risky behavior change or a non-local refactor + +## Invalid Reasons to Skip a Fix + +Never skip a confirmed finding for any of these reasons: + +- it seems low severity +- it is a secondary or convenience interaction +- it lives in a shared layer, wrapper, or component-library file +- it is outside the main component file or package the user first mentioned +- it is probably intentional without code evidence proving that intent diff --git a/skills/wix-app/references/editor-react-component/a11y-review/EXAMPLES.md b/skills/wix-app/references/editor-react-component/a11y-review/EXAMPLES.md new file mode 100644 index 00000000..cca5832a --- /dev/null +++ b/skills/wix-app/references/editor-react-component/a11y-review/EXAMPLES.md @@ -0,0 +1,224 @@ +# Examples + +## Native image -- missing alt + +```tsx +// Before + +``` + +Scanner result: `alt-text`, `high`, semantic type `img` + +```tsx +// After +Product photo +``` + +## Wrapper around image -- forwards props, missing alt at call site + +```tsx +export const ProductPhoto = (props) => ; +``` + +```tsx +// Before (call site) + +``` + +Scanner result: `alt-text`, `high` or `medium` depending on wrapper resolution + +```tsx +// After (call site -- wrapper already forwards alt via spread) + +``` + +## Wrapper around image -- does NOT forward alt + +```tsx +// Before (wrapper definition) +const Avatar = ({ src }: { src: string }) => ; +``` + +Scanner result: `alt-text`, `high` or `medium` + +```tsx +// After (wrapper definition -- add alt prop and forward it) +const Avatar = ({ src, alt }: { src: string; alt: string }) => {alt}; + +// Then at call site + +``` + +## Link-like component with invalid target + +```tsx +// Before + +``` + +Scanner result: `anchor-is-valid` if the component resolves or strongly infers to anchor semantics + +```tsx +// After (if it navigates) + + +// After (if it triggers an action -- change to button) + +``` + +## Invalid ARIA prop + +```tsx +// Before + +``` + +Scanner result: `aria-props`, usually `high` + +```tsx +// After + +``` + +## Invalid role + +```tsx +// Before + +``` + +Scanner result: `aria-role`, confidence depends on whether the component forwards DOM props + +```tsx +// After (if grouping) + + +// After (if no role needed) + +``` + +## ARIA on unsupported element + +```tsx +// Before + +``` + +Scanner result: `aria-unsupported-elements`, `high` + +```tsx +// After + +``` + +## Anchor used as button + +```tsx +// Before +Save +``` + +Scanner result: `anchor-is-valid`, `high` + +```tsx +// After + +``` + +## Conditional onClick on non-interactive element -- missing keyboard semantics + +The conditional spread pattern `{...(cond && { onClick })}` is common in this codebase and must be checked alongside direct `onClick` props. When the condition is true the element becomes interactive, so it needs full keyboard semantics even though it is non-interactive in other states. + +**Before applying any fix, run the pre-fix checks in [FIX-STRATEGIES.md](FIX-STRATEGIES.md#pre-fix-checks-for-adding-button-semantics-to-a-non-interactive-element).** + +### Simple case -- no interactive children, no existing tabIndex management + +```tsx +// Before +
+ {children} +
+``` + +Tier 2 finding: `high` confidence — native `
` conditionally receives `onClick` but has no `role`, `tabIndex`, or keyboard handler. + +```tsx +// After +const handleKeyDown: React.KeyboardEventHandler = e => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggleReducedMotion(); + } +}; + +
+ {children} +
+``` + +### Compound case -- element may contain a link + +When the same wrapper conditionally renders a `` or `` as a child, adding `role="button"` to the wrapper creates nested interactive elements — a WCAG violation that confuses keyboard navigation and screen readers. + +```tsx +// Before (BROKEN -- wrapper is a button containing a link) +const isClickable = animated && !forceReducedMotion; + +
+ {link ? {children} : {children}} +
+``` + +```tsx +// After -- exclude the link case from button semantics +const isClickable = animated && forceReducedMotion === undefined && !link; + +
+ {link ? {children} : {children}} +
+``` + +Key differences from the simple case: +- `isClickable` explicitly excludes `link` to avoid nested interactives. +- `forceReducedMotion === undefined` replaces `!forceReducedMotion` for type safety when the variable is `true | undefined`. + +### Existing tabIndex management on the same element + +When another utility already sets `tabIndex` on the same element (e.g., `getTabIndexAttribute(a11y)`), ensure the button-semantics `tabIndex: 0` does not silently override or conflict. Place the clickable spread **before** the a11y-driven spread so the a11y value wins when present. When the a11y source returns nothing, the clickable `tabIndex: 0` serves as fallback. + +```tsx +// tabIndex: 0 from clickable spread is overridden by getTabIndexAttribute(a11y) when a11y.tabIndex is set +
+``` diff --git a/skills/wix-app/references/editor-react-component/a11y-review/FIX-STRATEGIES.md b/skills/wix-app/references/editor-react-component/a11y-review/FIX-STRATEGIES.md new file mode 100644 index 00000000..89fd9e09 --- /dev/null +++ b/skills/wix-app/references/editor-react-component/a11y-review/FIX-STRATEGIES.md @@ -0,0 +1,206 @@ +# Fix Strategies + +This document describes how to fix each Tier 1 rule family. Apply these strategies only to findings with `high` or `medium` confidence. `low` confidence findings require reading the code first -- fix only when the semantic guess is confirmed. + +## General Principles + +- Read the surrounding code before editing. The scanner finding provides location and evidence, but the fix depends on context. +- Preserve existing behavior. A fix must not change what sighted users see or how the component functions. +- Prefer the simplest correct fix. Do not refactor unrelated code while fixing an a11y issue. +- **Call site vs definition**: When a wrapper hides the underlying element, decide where to fix: + - If the wrapper already accepts and forwards the missing prop (e.g., via spread), add the prop at the call site. + - If the wrapper does NOT accept or forward the missing prop, fix the wrapper definition to accept and forward it, then add the prop at the call site. + - If you are writing the component definition itself, the fix is to add the prop to the interface and forward it. Do not hardcode accessibility values (like alt text) inside reusable components -- they should come from the consumer. + +## `alt-text` + +### Native `` + +Add an `alt` prop with descriptive text. If the image is decorative, use `alt=""`. + +```tsx +// Before + + +// After (meaningful) +Product photo + +// After (decorative) + +``` + +### How to decide meaningful vs decorative + +- If the image conveys information not available from surrounding text, it is meaningful. +- If the image is purely visual (decorative border, background pattern, icon next to visible label text), it is decorative. +- When uncertain, default to meaningful with a descriptive alt. A redundant alt is better than a missing one. + +### Wrapper components + +When the finding is on a wrapper component (not a native ``), the fix depends on whether the wrapper already accepts and forwards `alt`. + +**Step 1: Check the wrapper definition.** Read the component source to see if it accepts `alt` in its props and passes it to the underlying ``. + +**If the wrapper already forwards `alt`**, add the prop at the call site: + +```tsx +// Wrapper already forwards alt via spread or explicit prop +export const ProductPhoto = (props) => ; + +// Before (call site) + + +// After (call site) + +``` + +**If the wrapper does NOT forward `alt`**, fix the wrapper definition to accept and forward it, then add the prop at the call site: + +```tsx +// Before (wrapper) +export const ProductPhoto = ({ src }) => ; + +// After (wrapper -- add alt to destructured props and forward it) +export const ProductPhoto = ({ src, alt }) => {alt}; + +// Then at the call site + +``` + +**If you are writing the component itself** (the finding is inside the component definition, not at a call site), the fix is to ensure the component accepts `alt` in its props interface and passes it through to the underlying ``: + +```tsx +// Before +const Avatar = ({ src }: { src: string }) => ; + +// After +const Avatar = ({ src, alt }: { src: string; alt: string }) => {alt}; +``` + +Do not hardcode alt text inside a reusable component. The alt value should come from the consumer. + +## `anchor-is-valid` + +### Missing `href` + +If the element is meant to navigate, add a real `href`. If it is meant to trigger an action, change it to a ` +``` + +### `href="#"` or `href="javascript:void(0)"` + +Replace with a real href or convert to a button. Never leave `href="#"` as a fix. + +```tsx +// Before +Learn more + +// After (if it navigates) +Learn more + +// After (if it triggers an action) + +``` + +### Wrapper components + +Check the wrapper definition first: + +- **If the wrapper forwards `href`**: fix the `href` value at the call site. +- **If the wrapper does not accept or forward `href`**: fix the wrapper to accept it, then provide a valid value at the call site. +- **If you are writing the wrapper itself**: ensure it accepts `href` in its props and passes it to the underlying ``. + +## `aria-props` + +### Invalid attribute name + +Replace the misspelled ARIA attribute with the correct spelling. Common typos: + +| Wrong | Correct | +|-------|---------| +| `aria-labeledby` | `aria-labelledby` | +| `aria-role` | `role` | +| `aria-roledescribedby` | `aria-roledescription` | + +```tsx +// Before +
Content
+ +// After +
Content
+``` + +## `aria-role` + +### Invalid role value + +Replace the invalid role with a valid ARIA role or remove it if no role is needed. + +```tsx +// Before +
Content
+ +// After (if it's a generic grouping) +
Content
+ +// After (if no role is needed) +
Content
+``` + +When choosing a replacement role, read the component's purpose. Do not guess -- pick the role that matches the actual behavior. + +## `aria-unsupported-elements` + +### ARIA on unsupported elements + +Remove the ARIA attributes from the unsupported element. If the intent was to label or describe something, move the ARIA attributes to a supported element that wraps or replaces the unsupported one. + +```tsx +// Before + + +// After + +``` + +If the ARIA attribute was meaningful (e.g., on a `` that should have been an `
`), change the element to the correct semantic one. + +## Tier 2 Fixes + +Tier 2 findings are discovered during the semantic review, not by the scanner. Apply fixes based on the specific checklist item: + +- **Missing accessible name on icon-only controls**: Add `aria-label` or visually hidden text. +- **ARIA on wrong element**: Move the attribute to the semantically correct inner element. +- **`display: none` hiding accessible content**: Switch to a visually-hidden CSS class or `aria-hidden` as appropriate. +- **Interactive element missing keyboard support**: Add `role`, `tabIndex`, and keyboard event handlers as needed. **Before applying this fix, run the pre-fix checks below.** + +For Tier 2, always read the code and confirm the issue before fixing. These are not scanner-automated and require judgment. + +### Pre-fix checks for adding button semantics to a non-interactive element + +When the fix involves adding `role="button"`, `tabIndex`, and keyboard handlers to a `
` or similar element, verify each of these before writing code. Skipping any check can introduce a new violation while fixing the original one. + +1. **Check for interactive children.** If the element conditionally or unconditionally contains ``, `