Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Docs/accessibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# ZCore dapp accessibility guidelines

This checklist applies to the authenticated dapp in `Front/`.

## Auth flows

- Keep wallet connection controls keyboard reachable and give them explicit accessible names.
- Announce wallet auth and signing errors with `role="alert"` and `aria-live="polite"`.
- Preserve visible focus on CTA buttons, secondary links, and wallet switch controls.

## Dashboard

- Wrap dapp pages in the `DappShell` landmark so keyboard users can skip directly to content.
- Expose credit score and tier progress with screen-reader labels, not only visual bars.
- Keep score and tier text available as real text, not only icons or decorative graphics.

## Visual checks

- Text and badge contrast should meet WCAG 2.1 AA contrast for normal text.
- Decorative grid, noise, and icons should be hidden from assistive technology with `aria-hidden`.
- Each page should keep a logical heading order from page title to section content.

## Local audit loop

1. Start the dapp locally.
2. Navigate `/login`, `/register`, and `/dashboard` using only the keyboard.
3. Confirm the skip link appears on first tab focus.
4. Confirm wallet auth errors are announced by a screen reader or browser accessibility tree.
5. Run Lighthouse or axe on the scoped pages before opening a UI PR.
18 changes: 16 additions & 2 deletions Front/components/connect-wallet-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ export function ConnectWalletButton({

if (!isReady) {
return (
<Button variant={variant} className={className} disabled>
<Button
variant={variant}
className={className}
disabled
aria-label="Loading available Stellar wallets"
aria-busy="true"
>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Loading wallets...
</Button>
Expand All @@ -27,7 +33,13 @@ export function ConnectWalletButton({

if (address) {
return (
<Button variant="outline" className={className} disabled>
<Button
variant="outline"
className={className}
disabled
aria-label={`Connected Stellar wallet ${walletName ?? "wallet"} ${address}`}
title={`Connected wallet ${address}`}
>
<Wallet className="mr-2 h-4 w-4" />
{walletName ? `${walletName}: ` : ""}
{truncateWallet(address)}
Expand All @@ -41,6 +53,8 @@ export function ConnectWalletButton({
className={className}
onClick={() => connect()}
disabled={isConnecting}
aria-label={isConnecting ? "Opening Stellar wallet selector" : "Connect Stellar wallet"}
aria-busy={isConnecting}
>
{isConnecting ? (
<>
Expand Down
10 changes: 9 additions & 1 deletion Front/components/dapp-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ interface DappShellProps {
export function DappShell({ children, className, withGrid = true }: DappShellProps) {
return (
<div className={cn("min-h-screen bg-black text-white relative", className)}>
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-50 focus:bg-white focus:px-4 focus:py-2 focus:text-xs focus:font-bold focus:uppercase focus:tracking-zk focus:text-black"
>
Skip to content
</a>
{withGrid && (
<>
<div className="pointer-events-none fixed inset-0 grid-bg opacity-30" aria-hidden />
<div className="pointer-events-none fixed inset-0 bg-noise opacity-[0.04]" aria-hidden />
</>
)}
<div className="relative">{children}</div>
<main id="main-content" className="relative" tabIndex={-1}>
{children}
</main>
</div>
)
}
1 change: 1 addition & 0 deletions Front/components/tier-badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export function TierBadge({ tier, className }: { tier: string; className?: strin

return (
<span
aria-label={`Credit tier ${TIER_LABELS[key]}`}
className={cn(
"inline-flex items-center border px-3 py-1 text-[10px] font-bold uppercase tracking-zk",
TIER_COLORS[key],
Expand Down
11 changes: 11 additions & 0 deletions Front/components/tier-progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export function TierProgress({
pointsToNext,
className,
}: TierProgressProps) {
const tierLabel =
currentTier in TIER_LABELS ? TIER_LABELS[currentTier as ProfileTier] : currentTier
const progressLabel = nextTier
? `${score} points, ${tierLabel} tier, ${pointsToNext} points to reach ${TIER_LABELS[nextTier]}`
: `${score} points, ${tierLabel} tier, maximum tier reached`

return (
<div className={cn("space-y-2", className)}>
<div className="flex justify-between text-xs">
Expand All @@ -29,6 +35,11 @@ export function TierProgress({
</div>
<div className="h-1.5 w-full overflow-hidden bg-white/[0.06]">
<div
role="progressbar"
aria-label={progressLabel}
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={Math.round(progress)}
className="h-full bg-gradient-to-r from-neutral-600 via-white to-neutral-400 transition-all duration-500 shadow-[0_0_12px_rgba(255,255,255,0.15)]"
style={{ width: `${progress}%` }}
/>
Expand Down
10 changes: 8 additions & 2 deletions Front/components/wallet-auth-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,14 @@ export function WalletAuthCard({ mode }: WalletAuthCardProps) {
)}

{error && (
<Alert variant="destructive" className="border-red-500/30 bg-red-500/10">
<XCircle className="h-4 w-4" />
<Alert
variant="destructive"
className="border-red-500/30 bg-red-500/10"
role="alert"
aria-live="polite"
aria-atomic="true"
>
<XCircle className="h-4 w-4" aria-hidden="true" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
Expand Down