coinbase / coinbase/onchainkit
Bug: TransactionButton has no accessible name while a transaction is approving or pending
- Dominant language
- TypeScript
- Stars
- 1k
- Forks
- 520
- Avg merge
- 32m
- Merged PRs (30d)
- 2
Description
### Describe the bug and the steps to reproduce it
`TransactionButton` swaps its entire label for a bare spinner while a
transaction is being submitted or is pending, with no accessible name left
on the button. A screen reader user who has already started a transaction
gets no indication that anything is happening, or that the button is even
still a button with a purpose, until it either succeeds or fails.
1. Run this repo's own playground app (`packages/playground`), which already
demonstrates `Transaction` with `TransactionButton` in
`packages/playground/components/demo/Transaction.tsx`, with a screen
reader running or the accessibility tree open in devtools.
2. Connect a wallet and click the demo's `TransactionButton` to submit a
transaction.
3. While the transaction is being signed/submitted and while it is pending
on-chain, inspect the button (`data-testid="ockTransactionButton_Button"`).
### What's the expected behavior?
The button should keep an accessible name throughout, for example
"Transact, pending" or similar, so a screen reader announces both that the
control is still a button and what state it is in. Marking it `aria-busy`
would also be reasonable in addition to a name.
### Actual behavior
`TransactionButton`'s content is computed in `buttonContent`
(`src/transaction/components/TransactionButton.tsx`, lines 63-75): when
`isLoading` is true, it returns `` and nothing else. The `Spinner`
component (`src/internal/components/Spinner.tsx`) renders two `
elements with no text, `aria-label`, or `role`. The `` element
itself (lines 108-127) sets no `aria-label` and has no visually-hidden text
fallback, so once `buttonContent` becomes the spinner, the button's
accessible name is empty. axe reports this as `button-name`.
```tsx
// TransactionButton.tsx
const buttonContent = useMemo(() => {
if (receipt) return 'View transaction';
if (errorMessage) return 'Try again';
if (isLoading) return ; // <- no text alternative
return idleText;
}, [isLoading, errorMessage, receipt, idleText]);
...
{buttonContent}
```
### What version of the libraries are you using?
@coinbase/onchainkit 1.1.2
### WCAG success criterion
4.1.2 Name, Role, Value.
### Rule
axe-core `button-name`.
### Suggested fix
Keep visually-hidden text on the button while `isLoading` is true (e.g. a
`sr-only`/`aria-label` reflecting the idle text plus "pending", or a fixed
string like "Transaction pending"), and consider `aria-busy="true"` on the
button for the duration. `Spinner` itself could also take an optional label
prop rather than always rendering as purely decorative markup.
Found while auditing wallet-connect kits for accessibility. A reproduction
script is available on request.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/transaction/components/TransactionButton.tsx, especially buttonContent and the button element, then inspect src/internal/components/Spinner.tsx. Reproduce the loading state in packages/playground/components/demo/Transaction.tsx and check the button with a screen reader, the accessibility tree, or axe. Done means the button retains an accessible name and communicates its pending state while signing, submitting, and awaiting confirmation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- accessibility, frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 85/100