Skip to content

Commit 6a12147

Browse files
feat: add WCAG 2.1 AA accessibility improvements to webapp (#17)
- Add skip-to-content link for keyboard navigation - Convert logo to h1 and panel titles to h2 for semantic structure - Add main landmark wrapper - Fix label/input associations with for/id attributes - Implement tablist pattern with ARIA roles for action buttons - Add live regions for activity log and warning box - Convert help icon to focusable button with keyboard support - Hide decorative icons from screen readers with aria-hidden - Add rel=noopener noreferrer and screen reader text for external link - Update JavaScript to manage ARIA states and focus on tab change Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Player 53627 <github.stagnate430@passmail.com>
1 parent b5a02c9 commit 6a12147

1 file changed

Lines changed: 115 additions & 54 deletions

File tree

docs/index.html

Lines changed: 115 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -277,23 +277,61 @@
277277
margin-bottom: 4px;
278278
}
279279

280-
.tooltip:hover::after {
280+
.tooltip:hover::after,
281+
.tooltip:focus::after,
282+
.tooltip:focus-visible::after {
281283
opacity: 1;
282284
visibility: visible;
283285
}
284286

285-
.help-icon {
286-
display: inline-block;
287-
width: 14px;
288-
height: 14px;
289-
line-height: 14px;
290-
text-align: center;
291-
font-size: 0.65rem;
287+
.help-btn {
288+
display: inline-flex;
289+
align-items: center;
290+
justify-content: center;
291+
width: 18px;
292+
height: 18px;
293+
font-size: 0.7rem;
292294
background: var(--bg-secondary);
295+
border: 1px solid var(--text-secondary);
293296
border-radius: 50%;
294297
color: var(--text-secondary);
295298
margin-left: 4px;
296299
cursor: help;
300+
padding: 0;
301+
font-family: inherit;
302+
}
303+
304+
.help-btn:focus-visible {
305+
outline: 2px solid var(--accent-cyan);
306+
outline-offset: 2px;
307+
}
308+
309+
.skip-link {
310+
position: absolute;
311+
top: -40px;
312+
left: 0;
313+
background: var(--accent-cyan);
314+
color: var(--bg-primary);
315+
padding: 8px 16px;
316+
z-index: 1000;
317+
text-decoration: none;
318+
border-radius: 0 0 8px 0;
319+
}
320+
321+
.skip-link:focus {
322+
top: 0;
323+
}
324+
325+
.sr-only {
326+
position: absolute;
327+
width: 1px;
328+
height: 1px;
329+
padding: 0;
330+
margin: -1px;
331+
overflow: hidden;
332+
clip: rect(0, 0, 0, 0);
333+
white-space: nowrap;
334+
border: 0;
297335
}
298336

299337
.warning-box {
@@ -369,102 +407,111 @@
369407
</style>
370408
</head>
371409
<body>
410+
<a href="#main" class="skip-link">Skip to main content</a>
372411
<div class="container">
373412
<header>
374-
<div class="logo">LIQ Flash Loan</div>
413+
<h1 class="logo">LIQ Flash Loan</h1>
375414
<button id="connectBtn" class="btn btn-connect">Connect Wallet</button>
376415
</header>
416+
<main id="main">
377417

378418
<!-- Status Panel -->
379-
<div class="panel">
380-
<div class="panel-title">
381-
<span class="status-dot loading" id="statusDot"></span>
419+
<section class="panel" aria-labelledby="status-heading">
420+
<h2 class="panel-title" id="status-heading">
421+
<span class="status-dot loading" id="statusDot" aria-hidden="true"></span>
382422
Pool Status
383-
</div>
423+
<span class="sr-only" id="statusAnnounce" aria-live="polite"></span>
424+
</h2>
384425
<div class="stats-grid">
385426
<div class="stat">
386-
<div class="stat-label">Pool Balance (tracked)</div>
387-
<div class="stat-value" id="poolBalance">-</div>
427+
<div class="stat-label" id="poolBalanceLabel">Pool Balance (tracked)</div>
428+
<div class="stat-value" id="poolBalance" aria-labelledby="poolBalanceLabel">-</div>
388429
</div>
389430
<div class="stat">
390-
<div class="stat-label">Actual USDC Balance</div>
391-
<div class="stat-value" id="actualBalance">-</div>
431+
<div class="stat-label" id="actualBalanceLabel">Actual USDC Balance</div>
432+
<div class="stat-value" id="actualBalance" aria-labelledby="actualBalanceLabel">-</div>
392433
</div>
393434
<div class="stat">
394-
<div class="stat-label">
435+
<div class="stat-label" id="syncStatusLabel">
395436
Sync Status
396-
<span class="help-icon tooltip" data-tooltip="Synced = poolBalance matches actual USDC balance">?</span>
437+
<button type="button" class="help-btn tooltip" data-tooltip="Synced = poolBalance matches actual USDC balance" aria-label="Help: Synced means poolBalance matches actual USDC balance">?</button>
397438
</div>
398-
<div class="stat-value" id="syncStatus">-</div>
439+
<div class="stat-value" id="syncStatus" aria-labelledby="syncStatusLabel">-</div>
399440
</div>
400441
<div class="stat">
401-
<div class="stat-label">Owner</div>
402-
<div class="stat-value" id="ownerAddress">-</div>
442+
<div class="stat-label" id="ownerLabel">Owner</div>
443+
<div class="stat-value" id="ownerAddress" aria-labelledby="ownerLabel">-</div>
403444
</div>
404445
</div>
405446
<div class="contract-info">
406-
Contract: <a href="https://etherscan.io/address/0xe9eb8a0f6328e243086fe6efee0857e14fa2cb87" target="_blank" id="contractLink">0xe9eb8a0f6328e243086fe6efee0857e14fa2cb87</a>
447+
Contract: <a href="https://etherscan.io/address/0xe9eb8a0f6328e243086fe6efee0857e14fa2cb87" target="_blank" rel="noopener noreferrer" id="contractLink">0xe9eb8a0f6328e243086fe6efee0857e14fa2cb87 <span class="sr-only">(opens in new tab)</span></a>
407448
</div>
408-
</div>
449+
</section>
409450

410451
<!-- Warning Box (shown when unsynced) -->
411-
<div class="warning-box hidden" id="warningBox">
412-
[!] Excess USDC detected. Call sync() to protect it from being drained.
452+
<div class="warning-box hidden" id="warningBox" role="alert" aria-live="assertive">
453+
Warning: Excess USDC detected. Call sync() to protect it from being drained.
413454
</div>
414455

415456
<!-- Actions Panel -->
416-
<div class="panel">
417-
<div class="panel-title">Actions</div>
418-
<div class="actions-row">
419-
<button class="action-btn" id="syncAction" data-action="sync">
420-
<div class="action-icon">[S]</div>
457+
<section class="panel" aria-labelledby="actions-heading">
458+
<h2 class="panel-title" id="actions-heading">Actions</h2>
459+
<div class="actions-row" role="tablist" aria-label="Action selection">
460+
<button class="action-btn" id="syncAction" data-action="sync" role="tab" aria-selected="false" aria-controls="syncPanel">
461+
<div class="action-icon" aria-hidden="true">[S]</div>
421462
<div>Sync</div>
422463
</button>
423-
<button class="action-btn" id="depositAction" data-action="deposit">
424-
<div class="action-icon">[+]</div>
464+
<button class="action-btn" id="depositAction" data-action="deposit" role="tab" aria-selected="false" aria-controls="depositPanel">
465+
<div class="action-icon" aria-hidden="true">[+]</div>
425466
<div>Deposit</div>
426467
</button>
427-
<button class="action-btn" id="withdrawAction" data-action="withdraw">
428-
<div class="action-icon">[-]</div>
468+
<button class="action-btn" id="withdrawAction" data-action="withdraw" role="tab" aria-selected="false" aria-controls="withdrawPanel">
469+
<div class="action-icon" aria-hidden="true">[-]</div>
429470
<div>Withdraw</div>
430471
</button>
431472
</div>
432-
</div>
473+
</section>
433474

434475
<!-- Action Forms -->
435-
<div class="panel hidden" id="syncPanel">
436-
<div class="panel-title">Sync Pool Balance</div>
476+
<section class="panel hidden" id="syncPanel" role="tabpanel" aria-labelledby="syncAction">
477+
<h2 class="panel-title">Sync Pool Balance</h2>
437478
<p style="color: var(--text-secondary); margin-bottom: 1rem; font-size: 0.9rem;">
438479
Sets poolBalance = USDC.balanceOf(this). Call after any direct USDC transfers.
439480
</p>
440481
<button class="btn btn-primary" id="syncBtn">Sync Now</button>
441-
</div>
482+
</section>
442483

443-
<div class="panel hidden" id="depositPanel">
444-
<div class="panel-title">Deposit USDC</div>
484+
<section class="panel hidden" id="depositPanel" role="tabpanel" aria-labelledby="depositAction">
485+
<h2 class="panel-title">Deposit USDC</h2>
445486
<div class="input-group">
446-
<label>Amount (USDC)</label>
447-
<input type="number" id="depositAmount" placeholder="1000.00" step="0.01">
487+
<label for="depositAmount">Amount (USDC)</label>
488+
<input type="number" id="depositAmount" placeholder="1000.00" step="0.01" aria-describedby="depositHelp">
489+
<span id="depositHelp" class="sr-only">Enter the amount of USDC you want to deposit</span>
448490
</div>
449491
<button class="btn btn-primary" id="depositBtn">Approve & Deposit</button>
450-
</div>
492+
</section>
451493

452-
<div class="panel hidden" id="withdrawPanel">
453-
<div class="panel-title">Withdraw USDC</div>
494+
<section class="panel hidden" id="withdrawPanel" role="tabpanel" aria-labelledby="withdrawAction">
495+
<h2 class="panel-title">Withdraw USDC</h2>
454496
<div class="input-group">
455-
<label>Amount (USDC)</label>
456-
<input type="number" id="withdrawAmount" placeholder="1000.00" step="0.01">
497+
<label for="withdrawAmount">Amount (USDC)</label>
498+
<input type="number" id="withdrawAmount" placeholder="1000.00" step="0.01" aria-describedby="withdrawHelp">
499+
<span id="withdrawHelp" class="sr-only">Enter the amount of USDC you want to withdraw</span>
457500
</div>
458501
<button class="btn btn-primary" id="withdrawBtn">Withdraw</button>
459-
</div>
502+
</section>
460503

461504
<!-- Activity Log -->
462-
<div class="panel">
463-
<div class="panel-title">Activity Log</div>
464-
<div class="log" id="log">
505+
<section class="panel" aria-labelledby="log-heading">
506+
<h2 class="panel-title" id="log-heading">Activity Log</h2>
507+
<div class="log" id="log" role="log" aria-live="polite" aria-relevant="additions">
465508
<div class="log-entry log-info">Welcome to LIQ Flash Loan Dashboard</div>
466509
</div>
467-
</div>
510+
</section>
511+
512+
<!-- Screen reader announcements -->
513+
<div id="srAnnounce" class="sr-only" aria-live="assertive" aria-atomic="true"></div>
514+
</main>
468515
</div>
469516

470517
<script type="module">
@@ -709,12 +756,26 @@
709756

710757
actionBtns.forEach(btn => {
711758
btn.addEventListener('click', () => {
712-
actionBtns.forEach(b => b.classList.remove('active'));
759+
// Update visual state
760+
actionBtns.forEach(b => {
761+
b.classList.remove('active');
762+
b.setAttribute('aria-selected', 'false');
763+
});
713764
btn.classList.add('active');
765+
btn.setAttribute('aria-selected', 'true');
766+
767+
// Show/hide panels
714768
const action = btn.dataset.action;
715769
syncPanel.classList.toggle('hidden', action !== 'sync');
716770
depositPanel.classList.toggle('hidden', action !== 'deposit');
717771
withdrawPanel.classList.toggle('hidden', action !== 'withdraw');
772+
773+
// Focus the first focusable element in the panel
774+
const activePanel = document.getElementById(action + 'Panel');
775+
if (activePanel) {
776+
const focusable = activePanel.querySelector('input, button');
777+
if (focusable) focusable.focus();
778+
}
718779
});
719780
});
720781

0 commit comments

Comments
 (0)