Skip to content

feat: add accessibility support to loading state components#388

Open
adrienzheng-cb wants to merge 3 commits intomasterfrom
adrien/loading-a11y-updates
Open

feat: add accessibility support to loading state components#388
adrienzheng-cb wants to merge 3 commits intomasterfrom
adrien/loading-a11y-updates

Conversation

@adrienzheng-cb
Copy link
Contributor

@adrienzheng-cb adrienzheng-cb commented Feb 10, 2026

Accessibility improvements:

  • Add role="status", aria-live="polite", and accessibilityLabel to
    Fallback, Spinner, and LottieStatusAnimation on web and mobile
  • LottieStatusAnimation auto-generates labels based on status
  • Update Fallback docs with accessibility guidance and grouping examples

Test improvements:

  • Add shape, width variant, and accessibility tests for Fallback
  • Add size variant and accessibility tests for Spinner
  • Add cardSuccess and status transition tests for LottieStatusAnimation

Refactoring:

  • Move useFallbackShape hook to @coinbase/cds-common for cross-platform use
  • Use shapeBorderRadius tokens in RemoteImage/RemoteImageGroup
  • deprecated all exports from LottieStatusAnimationProps file in commons
  • renamed LottieStatusAnimationType to LottieStatus
  • removed redudant usePreviousValue logic from ProgressBar

What changed? Why?

Root cause (required for bugfixes)

UI changes

iOS Old iOS New
old screenshot new screenshot
Android Old Android New
old screenshot new screenshot
Web Old Web New
old screenshot new screenshot

Testing

How has it been tested?

  • Unit tests
  • Interaction tests
  • Pseudo State tests
  • Manual - Web
  • Manual - Android (Emulator / Device)
  • Manual - iOS (Emulator / Device)

Testing instructions

Illustrations/Icons Checklist

Required if this PR changes files under packages/illustrations/** or packages/icons/**

  • verified visreg changes with Terran (include link to visreg run/approval)
  • all illustration/icons names have been reviewed by Dom and/or Terran

Change management

type=routine
risk=low
impact=sev5

automerge=false

@cb-heimdall
Copy link
Collaborator

cb-heimdall commented Feb 10, 2026

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 1
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1
CODEOWNERS 🟡 See below

🟡 CODEOWNERS

Code Owner Status Calculation
ui-systems-eng-team 🟡 0/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

addPreviousPercent(progress);
const previousPercent = getPreviousPercent() ?? 0;

const animatedProgress = useRef(new Animated.Value(previousPercent));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entire usePreviousValue logic is redundant. We just need to initialize the animated progress value based on disableAnimateOnMount and that is it.
We also use the same simple logic in ProgressCircle component. Not sure why ProgrssBar was over-complicated

percentage,
disableRandomRectWidth,
rectWidthVariant,
accessibilityLabel = 'Loading',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to test this part out with @samcartersmith since a customer might have several dozen fallbacks on the page at a time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hcopp That's why we went with this label vs an aria-live monstrous amount of noise. I think if there is a page with 20 fallbacks that are all persistent long enough for a user to arrow through them it will give a better understanding of the entire page is struggling to load.

@adrienzheng-cb adrienzheng-cb force-pushed the adrien/loading-a11y-updates branch 2 times, most recently from 349d63c to 907f793 Compare March 6, 2026 18:30
Accessibility improvements:
- Add role="status", aria-live="polite", and accessibilityLabel to
  Fallback, Spinner, and LottieStatusAnimation on web and mobile
- LottieStatusAnimation auto-generates labels based on status
- Update Fallback docs with accessibility guidance and grouping examples

Bug fixes:
- Fix ProgressBar callback re-animation issue by storing callbacks in refs
  instead of including them in useEffect dependencies

Test improvements:
- Add shape, width variant, and accessibility tests for Fallback
- Add size variant and accessibility tests for Spinner
- Add cardSuccess and status transition tests for LottieStatusAnimation

Refactoring:
- Move useFallbackShape hook to @coinbase/cds-common for cross-platform use
- Use shapeBorderRadius tokens in RemoteImage/RemoteImageGroup
- deprecated all exports from LottieStatusAnimationProps file in commons
- renamed LottieStatusAnimationType to LottieStatus
- removed redudant usePreviousValue logic from ProgressBar
:::
### Accessibility

Fallback includes a visually hidden "Loading" label by default, which screen readers will announce. You can customize this with the `accessibilityLabel` prop. Wrap Fallback in a live region container to announce loading state changes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to avoid mentioning the default label. We want to encourage customers to pass in their own a11y labels for i18n support. Your example below helps to encourage this. The default label is just a fallback (pun not intended) in case a label isn't passed in. It's better than nothing but not the ideal label.

Suggested change
Fallback includes a visually hidden "Loading" label by default, which screen readers will announce. You can customize this with the `accessibilityLabel` prop. Wrap Fallback in a live region container to announce loading state changes.
Fallback has an `accessibilityLabel` prop to describe the loading state for assistive technologies. Wrap Fallback in a live region container to announce loading state changes.

}
```

For multiple Fallbacks, add `aria-hidden` to all of them and provide your own visually hidden label:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit, can we rephrase this to inform customers about the prop but not require its usage? Using aria-hidden is helpful in some cases but in others may not be ideal.

Suggested change
For multiple Fallbacks, add `aria-hidden` to all of them and provide your own visually hidden label:
`aria-hidden` can be passed into a Fallback. This can be useful if you render multiple Fallbacks in an area and would prefer to have a parent container have the aria-label needed for the accessibility API. Whether it's a parent container or each Fallback, there needs to be an element with an `aria-label` indicating a loading state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

5 participants