Versioning and Deprecating Inaccessible Component APIs

Permalink to "Versioning and Deprecating Inaccessible Component APIs"

Design systems accumulate APIs that make inaccessible output easy: an optional ariaLabel that nobody sets, an onRowClick that turns rows into mouse-only targets, a statusColor prop with no text, a compact boolean that shrinks targets below 24 pixels. Fixing the component is often simple; changing the API that thousands of call sites use is not. A deliberate deprecation process — warn, provide the replacement, automate migration, measure adoption, then remove — lets a design system improve accessibility without breaking every product at once.

This guide belongs to design system tokens & component contracts.

Spec reference

Permalink to "Spec reference"
  • Semantic Versioning — breaking API changes (removing props, making props required) belong in major versions.
  • WCAG mapping — each deprecation should cite the criteria the old API violated (e.g. SC 2.1.1 for onRowClick, SC 1.4.1 for statusColor).
  • Accessibility debt — deprecated APIs still in use are debt items, tracked like any other.
Deprecating onRowClick over three releases Timeline: version 5.2 adds rowAction and a dev warning for onRowClick; 5.3 ships a codemod and migration guide; 5.4 tracks adoption; 6.0 removes onRowClick. Deprecating onRowClick over three releases5.2Replacement addedrowAction renders a named link or button; dev warning on onRowClick5.3Migration toolscodemod; guide; examples5.4Adoption trackingusage scanner across repositories6.0RemovalonRowClick removed; changelog cites SC 2.1.1
Warn, migrate, measure, remove.

When to use — and when not to

Permalink to "When to use — and when not to"

Deprecate APIs whose typical use produces inaccessible output that the component cannot fix internally. If the component can fix the behaviour without changing the API (for example, making clickable rows keyboard-operable automatically), do that instead.

Do not remove APIs without warnings and migration paths; teams will pin old versions and accessibility will regress further. Do not keep deprecated APIs forever; set and honour a removal version.

The misapplication: silently changing behaviour in a minor version (for example, making a required name prop throw), breaking builds across products without warning.

Abrupt removal versus staged deprecation Comparison: abrupt removal breaks builds and pushes teams to pin old versions; staged deprecation warns, provides tools, measures adoption and removes on a schedule. Abrupt removal versus staged deprecation✗ Abrupt removalBuilds break in a minor releaseTeams pin old versionsNo replacement documentedAdoption unknownAccessibility regresses through pinning✓ Staged deprecationWarning first; removal in a majorTeams migrate with a codemodReplacement and examples readyUsage tracked across reposAccessibility improves on schedule

Annotated code example

Permalink to "Annotated code example"
// 5.2: replacement added; old API warns in development
interface GridProps<Row> {
  /** @deprecated since 5.2 — mouse-only rows fail SC 2.1.1. Use rowAction. Removed in 6.0. */
  onRowClick?: (row: Row) => void;
  /** Renders the row header as a link or button with a name. */
  rowAction?: { kind: 'link'; href: (row: Row) => string } | { kind: 'button'; label: (row: Row) => string; onAction: (row: Row) => void };
}

function useDeprecations(props) {
  if (process.env.NODE_ENV !== 'production' && props.onRowClick && !warned.has('onRowClick')) {
    warned.add('onRowClick');
    console.warn('[Grid] onRowClick is deprecated (keyboard users cannot activate rows). Use rowAction. See https://ds.example.com/grid/migrate-row-click');
  }
}
// 5.3: jscodeshift codemod — onRowClick={(r) => navigate(`/inv/${r.id}`)} → rowAction link
export default function transformer(file, api) {
  const j = api.jscodeshift;
  return j(file.source).findJSXElements('Grid').forEach(path => {
    const attr = path.node.openingElement.attributes.find(a => a.name?.name === 'onRowClick');
    if (!attr) return;
    // Heuristic: navigation callbacks become link actions; others are flagged for manual review
    attr.name.name = 'rowAction';
    attr.value = j.jsxExpressionContainer(j.template.expression`{ kind: 'button', label: (row) => 'Open ' + row.id, onAction: ${attr.value.expression} }`);
  }).toSource();
}

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Deprecated API Problem for users Replacement Result
onRowClick Rows not keyboard operable rowAction link or button Named, focusable action
optional ariaLabel Unnamed grids required label Named grids
statusColor Colour-only status status with text and icon Status in words
compact boolean Tiny targets density token set 24 px targets
tooltip on header for help Hover-only help help toggletip prop Help on click and focus
Deprecation stages and their artefacts Matrix of stages — add replacement, warn, migrate, measure, remove — against code, documentation, tooling and communication. Deprecation stages and their artefactsCodeDocsToolingCommunicationAdd replacementNew propExamplesNoChangelogWarnDev warningDeprecation noticeNoRelease notesMigrateNoMigration guideCodemodOffice hoursMeasureNoNoUsage scannerDashboardRemoveMajor versionChangelog cites WCAGNoAnnouncement
Each stage has concrete deliverables.

Integration context

Permalink to "Integration context"

Deprecated APIs in use are tracked as debt, as in tracking accessibility debt in component libraries. Replacement APIs follow designing accessibility props for grid components. Keyboard behaviour changes should update keyboard contracts.

Gotchas

Permalink to "Gotchas"

Warning fatigue. Too many warnings get ignored. Warn once per session per API, and link to a specific migration page.

Heuristic codemods. Codemods cannot always infer intent (navigation vs action); flag uncertain cases with comments for manual review.

Pinned versions. Products that pin old majors stop receiving fixes; track version spread and support migration actively.

Worked example

Permalink to "Worked example"

A design system’s grid had onRowClick used in 230 places across 18 repositories. Keyboard users could not open records from most grids. The team added rowAction and a dev warning in 5.2, shipped a codemod and migration guide in 5.3 that converted 190 usages automatically, ran office hours for the remaining 40, tracked adoption with a repository scanner, and removed onRowClick in 6.0. By the removal date, only two usages remained, both in deprecated products. Keyboard access to records went from 20% of grids to all of them.

Common mistakes

Permalink to "Common mistakes"
  • Deprecating without a replacement ready to use.
  • Warnings without links to migration guides.
  • No usage tracking, so removal dates slip indefinitely.
  • Removal in minor versions, breaking builds and pushing teams to pin.

Testing checklist

Permalink to "Testing checklist"

Design system notes

Permalink to "Design system notes"

Maintain an “accessibility deprecations” page listing each deprecated API, its replacement, the criteria involved, codemod availability, adoption and removal version. Leadership reports can use the same data.

FAQ

Permalink to "FAQ"
Should accessibility fixes ever be breaking changes?

Behaviour fixes that do not change the API can ship in minor versions. API removals or new required props belong in majors, with deprecation first.

How long should deprecation last?

Typically one or two minor releases with warnings and tools, then removal in the next major — long enough for teams to migrate, short enough to matter.

What if a team cannot migrate in time?

Provide support, and track remaining usage as accessibility debt with a deadline.

Can I make a prop required without a major version?

Add runtime warnings first; making it required at the type level is breaking for TypeScript users.

How do I measure adoption?

Scan repositories for usage (code search, a script in CI) and report counts per release.

Should the changelog mention WCAG?

Yes. Citing criteria explains why the change matters and helps product teams prioritise.

What about APIs that are inaccessible only when misused?

Improve defaults and warnings first; deprecate only if misuse is the common case.

How do I communicate deprecations to designers?

Update design kits at the same time: remove deprecated variants (clickable-row grids, colour-only status badges) from libraries so new designs use the replacements.

What if a deprecated API is used by a product with no active team?

Track it as debt and plan a minimal migration, or document the product as frozen with known accessibility issues in its ACR.

Permalink to "Related"

← Back to Design System Tokens & Component Contracts