Skip to content

fix(components): component's own disabled state should always win over caller-supplied accessibilityState.disabled #220

Description

@Chibuzor-Nwemambu

Background

Raised by Claude's automated review on PR #218, discussed and decided 2026-07-06.

Button.tsx/IconButton.tsx and Input.tsx/TextArea.tsx all merge a caller-supplied accessibilityState with the component's own computed disabled value, but the caller's value currently wins when there's a conflict:

  • Button.tsx / IconButton.tsx: { disabled: disabled ?? false, ...pressableProps.accessibilityState }
  • Input.tsx: { disabled, ...rest.accessibilityState }
  • TextArea.tsx: { disabled, ...userAccessibilityState }

In all four, if a caller passes accessibilityState={{ disabled: true }} while the component's own disabled prop is false (or vice versa), the caller's value overwrites the component's own computed one.

Decision

The component's own disabled should always win, not the caller's. Rationale: disabled isn't just a label — it drives real behavior (editable, styling). If the caller's accessibilityState.disabled can disagree with that, a screen reader user can be told a field is disabled while it's actually fully interactive (or vice versa), which specifically misleads the exact audience this accessibility feature exists to serve. No realistic legitimate use case was found for letting a caller override just disabled this way.

This does not apply to other accessibilityState keys (selected, expanded, checked, etc.) — those should stay fully caller-overridable, since they don't have a competing "real" computed value the way disabled does.

Fix

Flip the spread order in all four files so the component's own disabled is applied last, e.g.:
```diff

  • accessibilityState={{ disabled, ...rest.accessibilityState }}
  • accessibilityState={{ ...rest.accessibilityState, disabled }}
    ```

Scope

Plan

  1. This issue's PR: source-only fix to all 4 files, no test changes.
  2. Follow-up: add regression tests to the existing test PRs — test(components): add tests for Button, Badge, Divider, Link, Typography #216 (`Button.test.tsx` has zero coverage of this merge behavior today) and test(components): add tests for Input, TextField, TextArea, Search, EDSProvider #218 (`Input.test.tsx`'s existing fix(components): Input clobbers a caller-supplied accessibilityState instead of merging it #217 regression test only covers a non-conflicting key like `selected` and wouldn't catch this; `TextArea.test.tsx` has zero coverage).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions