[audit] RichTextEditor: 8 open BLOCKs — 62.3/D (rubric v1.4); the package has no lint coverage
- Dominant language
- TypeScript
- Stars
- 13.2k
- Forks
- 1.1k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 669
Description
**Mode:** grading (on-demand, full pass) · **Rubric:** [v1.4](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric) · **Date:** 2026-08-17
**Component:** `RichTextEditor` (+ `RichTextView`, `RichTextEditorToolbar`) · **Package:** `@astryxdesign/richtext` (private, canary-only)
**Commit audited:** `6e7f89c16f5`
**Ledger row:** `richtext/RichTextEditor` — recorded in [`component-scores.json`](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#recording-an-audit)
## Headline
**62.3 / 100 → D**, with **8 open BLOCKs**. Fix the 8 BLOCKs and change nothing else → **projected ~78 / C**.
The editor itself is well built: the WCAG 2.1.2 Tab-escape plugin is the best implementation of that pattern in the repo, the Lexical effects all synchronise a genuinely external system, 86 tests assert the public contract, axe is green, and it reflows cleanly at 320 px. Almost every BLOCK below is a rule that **exists and would have caught this in `core`** — see [Root cause](#root-cause).
## Root cause — this package has no rule coverage
`eslint.config.js` scopes the whole `astryxConfig` (tokens, i18n, `require-base-props`, `no-classname-clobber`, `boolean-prop-naming`, physical properties, react-introspection) to **`packages/core/src/**` only**. `packages/richtext` is linted with the base config alone, so `pnpm lint:strict` is green while the code carries hardcoded strings, inline `
**Fix:** take an optional `label` (or `aria-label`/`aria-labelledby`) and pass it to `ContentEditable`; drop the baseline entry in the same PR.
### 3. §1 A3/A5 — `isReadOnly` and `isDisabled` are the same state, and neither is focusable
Measured in Chromium — the two props produce byte-identical output: `contenteditable="false"`, `aria-readonly="true"`, `tabIndex=-1`, `opacity: 0.5`, `cursor: not-allowed`. Consequences: disabled is never announced as disabled (no `aria-disabled`), and a **read-only editor cannot be reached by keyboard at all**, so its content can't be read or copied by a keyboard or screen-reader user.
**Fix:** keep the read-only surface focusable (`tabIndex=0`, `aria-readonly`, full-opacity text), and give disabled its own `aria-disabled` + dimmed treatment. `isReadOnly` should not borrow the disabled visual (see FIX 10).
### 4. §1 A10 — the block-format Selector is a 20 px-tall hit target
Measured at `pointer: fine` → `Paragraph` selector **68 × 20**; at `pointer: coarse` → **77 × 23**. Both below the WCAG 2.5.8 24 × 24 minimum. The icon buttons are 28 × 28 and **do not expand on coarse pointers** (identical at both), so nothing in the toolbar reaches the ~44 px touch target.
**Fix:** raise the selector to the `--size-element-sm` floor and add a coarse-pointer expansion for the whole toolbar row.
### 5. §2 T17 / §7 C15 — 15 inline `
Contributor guide
Assessment
This issue has not been assessed yet.