# 2.2 Transaction Signing
Users must be able to review and sign transactions using assistive technology.
## Success Criteria
### 2.2.1 Keyboard Accessible Actions (Level A)
**Requirement**: Sign, reject, and all transaction action buttons must be fully keyboard accessible with clear focus indicators.
**Intent**: Transaction signing is the most critical action in Web3. It must be accessible to all users.
**Benefits**:
- **Motor impairments**: Can sign without mouse
- **Blind users**: Can navigate to and activate buttons
- **All users**: Consistent interaction patterns
**Techniques**:
```tsx
<TransactionActions>
<button
onClick={handleReject}
onKeyDown={(e) => e.key === 'Enter' && handleReject()}
className="reject-button"
aria-label="Reject transaction"
>
Reject
</button>
<button
onClick={handleSign}
onKeyDown={(e) => e.key === 'Enter' && handleSign()}
className="sign-button"
aria-label="Sign and submit transaction"
// Primary action - should be focusable and prominent
autoFocus
>
Sign
</button>
</TransactionActions>
// CSS
.sign-button:focus-visible,
.reject-button:focus-visible {
outline: 3px solid #0066cc;
outline-offset: 2px;
}
```
**Failures**:
- Buttons don't respond to Enter key
- No visible focus indicators
- Focus order is illogical (sign before review)
---
### 2.2.2 Screen Reader Accessible Content (Level A)
**Requirement**: All transaction review content (recipient, amount, gas, contract details) must be fully accessible to screen readers with proper semantic structure.
**Intent**: Users must be able to review every detail before signing. Screen reader users need full access.
**Benefits**:
- **Blind users**: Can review all transaction details
- **Cognitive disabilities**: Structured content is easier to process
**Techniques**:
```tsx
<TransactionReview aria-labelledby="tx-title">
<h2 id="tx-title">Review Transaction</h2>
<dl className="transaction-details">
<div className="detail-row">
<dt>To</dt>
<dd>
<AddressDisplay
address={tx.to}
ensName={tx.toEns}
/>
</dd>
</div>
<div className="detail-row">
<dt>Amount</dt>
<dd aria-label={`${tx.amount} ${tx.symbol}`}>
{tx.amount} {tx.symbol}
</dd>
</div>
<div className="detail-row">
<dt>Network Fee (estimated)</dt>
<dd aria-label={`Approximately ${tx.feeUsd} dollars`}>
~${tx.feeUsd} ({tx.feeEth} ETH)
</dd>
</div>
<div className="detail-row">
<dt>Total</dt>
<dd aria-label={`${tx.total} ${tx.symbol}, approximately ${tx.totalUsd} dollars`}>
{tx.total} {tx.symbol} (~${tx.totalUsd})
</dd>
</div>
</dl>
{tx.warnings.length > 0 && (
<div role="alert" aria-live="polite">
<h3>Warnings</h3>
<ul>
{tx.warnings.map(warning => (
<li key={warning.id}>{warning.message}</li>
))}
</ul>
</div>
)}
</TransactionReview>
```
**Failures**:
- Transaction details in non-semantic divs
- Important info hidden in tooltips
- Visual-only layout (tables without proper markup)
---
### 2.2.3 Adjustable Time Limits (Level AA)
**Requirement**: When transactions have time limits (quote expiry, gas price validity), users can request more time or are warned before expiry.
**Intent**: Some users need more time to review. Expired quotes shouldn't surprise users.
**Benefits**:
- **Cognitive disabilities**: More time to understand and decide
- **Blind users**: More time to navigate with screen reader
- **All users**: Awareness of time constraints
**Techniques**:
```tsx
<TransactionTimer
expiresAt={quote.expiresAt}
onExpiring={() => announceToScreenReader('Quote expires in 30 seconds')}
onExpired={handleQuoteExpired}
>
{({ timeRemaining, isExpiring }) => (
<div
role="timer"
aria-live={isExpiring ? 'assertive' : 'polite'}
aria-label={`Quote valid for ${timeRemaining} seconds`}
>
{isExpiring && (
<span className="warning">Quote expiring soon: </span>
)}
{formatTime(timeRemaining)}
{isExpiring && (
<button onClick={refreshQuote}>
Refresh Quote
</button>
)}
</div>
)}
</TransactionTimer>
```
**Failures**:
- Quote expires without warning
- No way to extend or refresh
- Timer not announced to screen readers
---
### 2.2.4 Multi-Step State Maintenance (Level AA)
**Requirement**: Multi-step signing processes (approve + swap, batch transactions) must maintain state and allow users to resume if interrupted.
**Intent**: Complex transactions shouldn't need to start over if a user needs a break or loses focus.
**Benefits**:
- **Cognitive disabilities**: Can take breaks without losing progress
- **Motor impairments**: Less repetitive work if interrupted
- **All users**: Better UX for complex operations
**Techniques**:
```tsx
<MultiStepTransaction
steps={[
{ id: 'approve', label: 'Approve USDC', status: 'complete' },
{ id: 'swap', label: 'Swap tokens', status: 'pending' },
]}
>
{/* Progress indicator */}
<div role="progressbar" aria-valuenow={1} aria-valuemax={2}>
<span className="sr-only">Step 1 of 2 complete</span>
</div>
<ol aria-label="Transaction steps">
{steps.map((step, index) => (
<li
key={step.id}
aria-current={step.status === 'pending' ? 'step' : undefined}
>
<span className="step-number">{index + 1}</span>
<span className="step-label">{step.label}</span>
<span className="step-status">
{step.status === 'complete' && '✓ Complete'}
{step.status === 'pending' && 'Waiting...'}
{step.status === 'upcoming' && 'Upcoming'}
</span>
</li>
))}
</ol>
{/* Allow resuming */}
<button onClick={resumeFromLastStep}>
Continue from Step {currentStep}
</button>
</MultiStepTransaction>
```
**Failures**:
- Progress lost on page refresh
- No indication of current step
- Cannot skip already-completed steps
---
### 2.2.5 Hardware Wallet Screen Reader Support (Level AAA)
**Requirement**: Hardware wallet signing flows provide screen reader guidance throughout the external device interaction.
**Intent**: Hardware wallets require physical interaction. Users need guidance on what's happening.
**Benefits**:
- **Blind users**: Know when to interact with device
- **All users**: Clear status during external signing
**Techniques**:
```tsx
<HardwareWalletFlow wallet="ledger" status={signingStatus}>
<div role="status" aria-live="assertive">
{status === 'connecting' && (
<p>Connecting to Ledger device. Please unlock your device.</p>
)}
{status === 'awaiting_confirmation' && (
<p>
Please review and confirm the transaction on your Ledger device.
Press both buttons to confirm, or the right button to reject.
</p>
)}
{status === 'signing' && (
<p>Signing transaction. Do not disconnect your device.</p>
)}
{status === 'complete' && (
<p>Transaction signed successfully.</p>
)}
{status === 'error' && (
<p role="alert">Error: {errorMessage}. Please try again.</p>
)}
</div>
</HardwareWalletFlow>
```
**Failures**:
- No guidance during device interaction
- Status changes not announced
- Error states not communicated
---
## Testing Checklist
- [ ] Sign/Reject buttons keyboard accessible
- [ ] Focus indicators visible (3px minimum)
- [ ] All transaction details screen reader accessible
- [ ] Definition list markup for key-value pairs
- [ ] Timer warns before expiry
- [ ] Multi-step progress is clear
- [ ] Hardware wallet flow has status announcements
## Related Components
- [TransactionSummary.tsx](../../components/TransactionSummary.tsx)
- [TransactionActions.tsx](../../components/TransactionActions.tsx)
- [HardwareWalletFlow.tsx](../../components/HardwareWalletFlow.tsx)