feat(ui): accessibility audit and UX optimization#34
Conversation
- Consolidate 52 hardcoded color tokens to theme variables - Selective text-xs sizing improvements for mobile readability - Add skip-to-content link in dashboard layout - Add iOS safe area insets for mobile bottom nav - Fix hover-only affordances (chip remove buttons) - Add keyboard navigation for interactive elements - Add form autocomplete attributes - Add aria-live regions for dynamic content All 742 tests pass, zero regressions
- Fix CSS contrast issues (opacity patterns → contrast-safe tokens) - Add touch swipe gestures to SwipeablePlayerRow (raw touch events) - Respect prefers-reduced-motion in swipe animations All 742 tests pass, zero regressions
Replaces generic 'Search results updated' with 'Showing N of M players' to satisfy WCAG SC 4.1.3 (Status Messages) — screen readers now announce the actual filtered count when search/filter state changes.
There was a problem hiding this comment.
Pull request overview
This PR addresses pre-alpha UI accessibility gaps and mobile UX issues by replacing hardcoded/opacity-based styling with theme tokens, improving readable sizing/contrast, and adding keyboard/screen-reader enhancements including swipe-to-draft on mobile rankings.
Changes:
- Standardized UI text sizing and replaced hardcoded grays/whites with theme tokens (e.g.,
text-muted-foreground) across trade/roster/draft/dashboard UI. - Improved accessibility semantics: skip-to-content link, safe-area viewport support, better form labeling and ARIA attributes, and loading
aria-busyregions. - Added mobile swipe gesture support to draft rankings rows and wired
prefers-reduced-motioninto the swipe animation.
Reviewed changes
Copilot reviewed 46 out of 46 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/waiver/faab-bid-slider.tsx | Increased helper text size for readability. |
| src/components/transactions/transaction-row.tsx | Increased timestamp text size for readability. |
| src/components/transactions/transaction-details.tsx | Increased secondary detail text size for readability. |
| src/components/trade/trade-partner-finder.tsx | Replaced hardcoded gray with theme token; increased description/detail text sizes. |
| src/components/trade/trade-explanation.tsx | Replaced hardcoded gray with theme token; increased explanatory text size. |
| src/components/trade/trade-builder.tsx | Replaced hardcoded gray with theme token; increased helper/empty-state text sizes. |
| src/components/trade/player-picker.tsx | Replaced hardcoded gray with theme token across controls and results. |
| src/components/trade/player-chip.tsx | Made drag/remove affordances visible on touch/focus; replaced hardcoded gray with tokens. |
| src/components/trade/pick-chip.tsx | Same as player chips: touch/focus visibility + tokenized colors. |
| src/components/trade/format-toggle.tsx | Replaced hardcoded gray with theme token for inactive state. |
| src/components/trade/fairness-meter.tsx | Replaced hardcoded gray with theme token in labels. |
| src/components/trade/bias-slider.tsx | Replaced hardcoded gray with theme token; standardized label colors. |
| src/components/roster/lineup-slot.tsx | Improved empty-slot styling and contrast; increased metadata/injury text sizes. |
| src/components/roster/lineup-explanation.tsx | Increased explanatory text sizes for readability. |
| src/components/roster/lineup-comparison.tsx | Replaced hardcoded colors with tokens; improved contrast/readability in summary cards. |
| src/components/roster/apply-optimization-button.tsx | Increased secondary text sizes in dialog content. |
| src/components/layout/user-menu.tsx | Increased email/subtext size for readability. |
| src/components/layout/mobile-nav.tsx | Added safe-area bottom padding to loading fallback nav. |
| src/components/layout/mobile-nav-animated.tsx | Added safe-area bottom padding; slightly increased label text size. |
| src/components/gamification/streak-indicator.tsx | Increased warning text size for readability. |
| src/components/gamification/achievement-badge.tsx | Increased unlocked date text size for readability. |
| src/components/feedback/feedback-form.tsx | Added explicit label associations/ids and improved ARIA for star rating. |
| src/components/draft/swipeable-player-row.tsx | Implemented swipe-to-draft gesture + keyboard support + reduced-motion transitions. |
| src/components/draft/simulation-overlay.tsx | Increased simulation status text size. |
| src/components/draft/rankings-list.tsx | Updated drafted-row styling; added SR-only assertive aria-live for celebrations; passed new props to swipe row. |
| src/components/draft/rankings-controls.tsx | Added SR-only aria-live announcing “Showing N of M players”. |
| src/components/draft/pick-explanation.tsx | Increased algorithm info text size. |
| src/components/draft/my-team-sidebar.tsx | Increased sidebar metadata text size. |
| src/components/draft/keeper-section.tsx | Increased keeper metadata text size. |
| src/components/draft/explanation-panel.tsx | Increased explanatory paragraph text size. |
| src/components/draft/draft-view-tabs.tsx | Adjusted responsive grid to include an md breakpoint for improved layout. |
| src/components/draft/draft-rankings.tsx | Passed result/total counts into RankingsControls for better aria-live messaging. |
| src/app/layout.tsx | Added viewport config for iOS safe-area support (viewport-fit=cover). |
| src/app/(sandbox)/draft-sandbox/sandbox-draft-view.tsx | Updated responsive grid to include md breakpoint. |
| src/app/(sandbox)/draft-sandbox/league-form.tsx | Increased helper text sizes for readability. |
| src/app/(dashboard)/waivers/waivers-client.tsx | Updated responsive grid to include md breakpoint. |
| src/app/(dashboard)/waivers/loading.tsx | Added aria-live/aria-busy to loading container; updated responsive grid. |
| src/app/(dashboard)/trade/trade-client.tsx | Replaced hardcoded gray with theme token; increased methodology/caveats text sizes. |
| src/app/(dashboard)/trade/loading.tsx | Added aria-live/aria-busy to loading container. |
| src/app/(dashboard)/roster/loading.tsx | Added aria-live/aria-busy to loading container. |
| src/app/(dashboard)/layout.tsx | Added skip-to-content link and main anchor target id. |
| src/app/(dashboard)/draft/loading.tsx | Added aria-live/aria-busy to loading container; updated responsive grid. |
| src/app/(dashboard)/dashboard/page.tsx | Increased secondary description text size. |
| src/app/(dashboard)/dashboard/league-card.tsx | Increased stat subtext size. |
| src/app/(dashboard)/connect/page.tsx | Improved autocomplete, added keyboard operability/ARIA for selectable league cards. |
| src/app/(auth)/login/page.tsx | Added email autocomplete to login form field. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| {isDraftable && ( | ||
| <button | ||
| type="button" | ||
| onClick={() => onDraft(playerId)} | ||
| className="absolute left-0 top-0 h-full w-24 bg-green-600 text-sm font-semibold text-white" | ||
| aria-label={`Draft ${playerName}`} | ||
| > | ||
| Draft | ||
| </button> | ||
| )} |
There was a problem hiding this comment.
The left-side "Draft" is always rendered (when isDraftable), even when it’s fully covered by the row content at offsetX=0. That keeps it in the tab order and focusable while not visually discoverable, which is an a11y issue. Consider only rendering/enabling it once the swipe reveal is active, or set tabIndex={-1}/aria-hidden/disabled (and possibly pointer-events-none) while offsetX is below the reveal threshold.
| <div | ||
| role="button" | ||
| tabIndex={0} | ||
| aria-label={`${playerName}, ${position}, VBD ${vbd.toFixed(1)}${isDraftable ? ', press Enter to draft' : ''}`} | ||
| onClick={handleClick} | ||
| onKeyDown={handleKeyDown} | ||
| onTouchStart={handleTouchStart} | ||
| onTouchMove={handleTouchMove} | ||
| onTouchEnd={handleTouchEnd} | ||
| onTouchCancel={handleTouchEnd} | ||
| style={{ |
There was a problem hiding this comment.
role="button"/tabIndex={0} and keyboard handlers are applied even when isDraftable is false, which makes non-actionable rows appear interactive to keyboard and screen-reader users. Consider conditionally setting tabIndex/role (or aria-disabled) and only attaching click/key handlers when drafting is actually available.
- Add dev-only magic link login bypass with Sleeper auto-connect - Add ADP proxy route to bypass browser CORS in sandbox - Use upsert for algorithm cache writes (avoids delete+insert race) - Use no-cache for getAllPlayers (18MB exceeds Next.js 2MB limit) - Extract viewport to exported const (Next.js best practice) - Polish simulation overlay and risk slider responsive layout - Update CSP for PostHog assets, FFC, and web workers - Pre-fill sandbox league ID from NEXT_PUBLIC_SANDBOX_LEAGUE_ID
Improve mobile touch targets to 44px minimum across roster week selector, waivers inputs, trade builder buttons, feedback stars, bias slider thumb, and format toggle labels. Bump muted-foreground contrast from 0.70 to 0.75 and swap green/red-400 to 500 for WCAG compliance. Fix roster optimizer empty slot rendering when starter count exceeds player count. Add horizontal scroll for draft position filters and week selector on mobile. Add E2E tests for zero-improvement badge suppression and empty slot display.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Simplify CLI architecture with dedicated modules for args parsing, screenshot capture, and dev server. Untrack generated-prompts directory as output artifacts should not be version controlled.
next start sets NODE_ENV=production, which caused the dev-login endpoint to return 404. The capture script then silently continued without auth, so protected routes rendered login pages instead of actual content. Pass ENABLE_DEV_LOGIN=true from the serve script and check for it in the dev-login gate alongside NODE_ENV.
Capture script was screenshotting pages before data finished loading, resulting in skeleton placeholders being flagged as visual bugs. Now waits for networkidle + skeleton element removal before capture.
Why
Pre-alpha UI audit surfaced accessibility gaps and mobile UX issues — hardcoded colors failing contrast checks, missing keyboard/screen-reader support, no touch gestures for core interactions, and several WCAG violations. These need to be resolved before any alpha user testing.
What Changed
Wave 1 — Accessibility foundation (
fix(ui))aria-liveregions, formautocompleteattributes, and selectivetext-xssizing improvementsWave 2 — Structural improvements (
feat(ui))SwipeablePlayerRow(draft action on swipe)prefers-reduced-motioninto swipe animationsWave 3 — Screen reader polish (
fix(a11y))aria-liveregion (WCAG SC 4.1.3)All 742 tests pass across all three waves, zero regressions.
Learnings
opacity-60on text creates contrast ratios that depend on the background — unreliable for WCAG AA. Switched to explicittext-muted-foreground/text-mutedtokens that guarantee 4.5:1 regardless of context.touchstart/touchmove/touchendwithuseReftracking to avoid interfering with scroll momentum.env(safe-area-inset-bottom)needs to be composed with existing padding viacalc()— can't just swap in the env value or you lose the original spacing.pnpm validate)