← Files WixARCHIVED FILE

skills/wix-app/references/editor-react-component/ACCESSIBILITY.md

4.43 KB · Oct 5, 2026 · 12:03 UTC

↓ Download file

# Accessibility (ARIA)

Rules and patterns for ARIA accessibility support in Editor React components.

---

## Rules

### All ARIA Attributes Through the `a11y` Prop

- **NEVER** add individual ARIA properties like `ariaLabel?: string`, `ariaDescribedBy?: string`, `role?: string`, etc. to a component's props interface.
- **ALL** accessibility attributes MUST come through the `a11y?: A11y` prop.
- The `A11y` type includes: `ariaLabel`, `ariaDescribedBy`, `ariaLabelledBy`, `role`, `ariaHidden`, `ariaLive`, and all other ARIA attributes.
- Users provide ARIA values through the `a11y` prop: `<Component a11y={{ ariaLabel: "..." }} />`

### ARIA Label Rules

ARIA labels are user-facing content and must NEVER be hardcoded as string literals in JSX.

**Three patterns (in order of preference):**

1. **No aria-label (preferred)** — Visible text content is sufficient:

   ```tsx
   <Button>Play</Button> // Visible text ✅
   ```

2. **User-configurable** — Via a11y prop (for root or element-specific labels, per requirements):

   ```tsx
   // User provides: <ComponentName a11y={{ ariaLabel: "Background music" }} />
   // Component applies to root (using the a11y-to-HTML conversion utility):
   {...(a11y && convertA11yKeysToHtmlFormat(a11y))}

   // Or for inner elements (if requirements specify):
   // elementProps.button.a11y gets passed to the button element
   ```

3. **System-required** — Via constants.ts (when requirements specify non-configurable label):

   ```tsx
   // constants.ts
   export const ARIA_LABELS = {
     playButton: 'Play audio',
     muteButton: 'Mute audio',
   } as const;

   // component.tsx
   import { ARIA_LABELS } from './constants';
   <Button aria-label={ARIA_LABELS.playButton}>▶</Button>;
   ```

**❌ NEVER hardcode string literals:**

```tsx
// ❌ WRONG - hardcoded strings
<Button aria-label="Play">▶</Button>
<Button aria-label={isPlaying ? "Pause" : "Play"}>▶</Button>
<Slider aria-label="Volume" />
```

**Why this matters:**

- Hardcoded strings cannot be translated or customized
- User-provided labels take precedence when available
- Constants allow centralized management and potential future translation

### Detecting Icon-Only Interactive Elements

**Icon-only elements require aria-labels.** Detect with this checklist:

| Element Content       | Needs aria-label? | Example                                   |
| --------------------- | ----------------- | ----------------------------------------- |
| Visible text only     | ❌ No             | `<Button>Play</Button>`                   |
| Icon component + text | ❌ No             | `<Button><PlayIcon />Play</Button>`       |
| Icon/emoji only       | ✅ YES            | `<Button>▶</Button>`                     |
| Image only            | ✅ YES            | `<Button><img src="play.png" /></Button>` |
| SVG only              | ✅ YES            | `<Button><svg>...</svg></Button>`         |

**Critical:** If button/link contains ONLY visual elements (icons, emojis, images, SVG) with NO text node, it MUST have aria-label from constants.

**Pattern for dynamic labels:**

```tsx
// constants.ts
export const ARIA_LABELS = {
  playButton: 'Play audio',
  pauseButton: 'Pause audio',
} as const;

// component.tsx
<Button
  aria-label={isPlaying ? ARIA_LABELS.pauseButton : ARIA_LABELS.playButton}
>
  {isPlaying ? '⏸' : '▶'}
</Button>;
```

---

## Common Mistake: Individual ARIA Properties Instead of A11y Type

**❌ Wrong:**

```typescript
import type { Direction } from '@wix/editor-react-types';

export interface TabsProps {
  id?: string;
  className?: string;
  direction?: Direction;
  ariaLabel?: string; // ❌ Individual ARIA prop
  ariaDescribedBy?: string; // ❌ Individual ARIA prop
  role?: string; // ❌ Individual ARIA prop
}
```

**✅ Correct:**

```typescript
import type { A11y, Direction } from '@wix/editor-react-types';
import { convertA11yKeysToHtmlFormat } from '@wix/react-component-utils';

export interface TabsProps {
  id?: string;
  className?: string;
  direction?: Direction;
  a11y?: A11y;  // ✅ All ARIA attributes through this prop
}

// Usage:
<Tabs a11y={{ ariaLabel: "Navigation", role: "navigation" }} />

// In component:
{...(a11y && convertA11yKeysToHtmlFormat(a11y))}
```

**Why:** The `A11y` type from `@wix/editor-react-types` provides a standardized way to handle ALL ARIA attributes (ariaLabel, ariaDescribedBy, ariaLabelledBy, role, ariaHidden, ariaLive, etc.). Individual ARIA props fragment the API and make it harder to use.

SHA-256: 48d730f6341eb187bd5912fd0f439e944b6b880af0afd275fda5f299a522b784