# 2.1 Wallet Connection
Wallet connection flows must be fully keyboard and switch accessible.
## Success Criteria
### 2.1.1 Keyboard Navigable Wallet Selection (Level A)
**Requirement**: The wallet selection interface must be fully navigable using only a keyboard. Users must be able to tab through options, select with Enter/Space, and cancel with Escape.
**Intent**: Modal wallet selectors are often click-only. Keyboard users must be able to connect.
**Benefits**:
- **Motor impairments**: Can connect without mouse
- **Blind users**: Can navigate with screen reader
- **Power users**: Faster keyboard-based workflows
**Techniques**:
```tsx
<WalletModal
isOpen={isOpen}
onClose={handleClose}
aria-labelledby="wallet-modal-title"
>
<h2 id="wallet-modal-title">Connect Wallet</h2>
<div role="listbox" aria-label="Available wallets">
{wallets.map((wallet, index) => (
<button
key={wallet.id}
role="option"
aria-selected={selectedWallet === wallet.id}
onClick={() => connectWallet(wallet)}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') focusNext();
if (e.key === 'ArrowUp') focusPrev();
if (e.key === 'Escape') handleClose();
}}
tabIndex={index === 0 ? 0 : -1}
>
<img src={wallet.icon} alt="" aria-hidden="true" />
<span>{wallet.name}</span>
{!wallet.installed && <span>(Not installed)</span>}
</button>
))}
</div>
<button onClick={handleClose}>Cancel</button>
</WalletModal>
```
**Failures**:
- Wallet options only respond to click
- No keyboard focus indicators
- Cannot close modal with Escape
- Tab order doesn't follow visual order
---
### 2.1.2 Focus Trapping (Level A)
**Requirement**: When wallet connection modals open, focus must be trapped within the modal and returned to the trigger element when closed.
**Intent**: Focus escaping to hidden content behind modals is disorienting for screen reader users.
**Benefits**:
- **Blind users**: Focus stays in context
- **Keyboard users**: Predictable navigation
**Techniques**:
```tsx
import { FocusTrap } from '@headlessui/react';
// Or implement manually:
function WalletModal({ isOpen, onClose, children }) {
const modalRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (isOpen) {
// Store current focus
triggerRef.current = document.activeElement as HTMLElement;
// Focus first focusable element
modalRef.current?.querySelector('button')?.focus();
} else {
// Return focus to trigger
triggerRef.current?.focus();
}
}, [isOpen]);
// Handle Tab key to trap focus
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Tab') {
const focusable = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
// ... trap focus logic
}
};
return (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
onKeyDown={handleKeyDown}
>
{children}
</div>
);
}
```
**Failures**:
- Focus moves behind open modal
- Focus not returned after closing
- Multiple focus traps conflict
---
### 2.1.3 QR Code Alternatives (Level AA)
**Requirement**: When QR codes are used for wallet connection (WalletConnect, mobile linking), a keyboard-accessible alternative must be provided.
**Intent**: QR codes require visual scanning and camera access. Alternatives ensure all users can connect.
**Benefits**:
- **Blind users**: Cannot scan QR codes
- **Desktop users**: May not have camera
- **All users**: Alternative connection methods
**Techniques**:
```tsx
<WalletConnectFlow>
<div role="tablist">
<button role="tab" aria-selected={tab === 'qr'} onClick={() => setTab('qr')}>
QR Code
</button>
<button role="tab" aria-selected={tab === 'link'} onClick={() => setTab('link')}>
Copy Link
</button>
</div>
{tab === 'qr' && (
<div role="tabpanel">
<img src={qrCodeUrl} alt="WalletConnect QR code" />
<p>Scan with your mobile wallet</p>
</div>
)}
{tab === 'link' && (
<div role="tabpanel">
<input
type="text"
value={connectionUri}
readOnly
aria-label="WalletConnect connection URI"
/>
<button
onClick={copyToClipboard}
aria-label="Copy connection link"
>
Copy Link
</button>
<p>Paste this link in your mobile wallet app</p>
</div>
)}
</WalletConnectFlow>
```
**Failures**:
- QR code is only connection option
- Copy link not keyboard accessible
- No instructions for manual connection
---
### 2.1.4 Voice-Controlled Connection (Level AAA)
**Requirement**: Wallet connection can be initiated and completed using voice control software.
**Intent**: Users with severe motor impairments may rely entirely on voice control.
**Benefits**:
- **Severe motor impairments**: Full voice control access
- **Hands-free operation**: Use in various contexts
**Techniques**:
```tsx
// Ensure all interactive elements have visible labels
<button
aria-label="Connect MetaMask wallet"
// Voice control can target: "Click Connect MetaMask wallet"
>
<MetaMaskIcon aria-hidden="true" />
Connect MetaMask
</button>
// Avoid icon-only buttons
// ❌ <button><Icon /></button>
// ✅ <button><Icon /> Connect</button>
// Number overlay support (Dragon NaturallySpeaking)
// Ensure buttons are large enough targets (44x44px minimum)
```
**Failures**:
- Icon-only buttons without labels
- Very small touch/click targets
- Dynamic content that changes labels
---
## Testing Checklist
- [ ] Can open wallet modal with keyboard
- [ ] Can navigate wallet options with arrow keys
- [ ] Can select wallet with Enter/Space
- [ ] Can close modal with Escape
- [ ] Focus trapped within modal
- [ ] Focus returns to trigger on close
- [ ] QR code has copy link alternative
- [ ] All buttons have visible text labels
## Related Components
- [WalletModal.tsx](../../components/WalletModal.tsx)
- [WalletButton.tsx](../../components/WalletButton.tsx)
- [ConnectButton.tsx](../../components/ConnectButton.tsx)