From fc982bee1b58a7b778ef3c16bbd9bd381bcd84f0 Mon Sep 17 00:00:00 2001
From: Jake Jarvis
Date: Thu, 5 Mar 2026 18:20:25 -0500
Subject: [PATCH] Comprehensive Web Interface Guidelines audit: accessibility,
motion, and typography
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add global MotionConfig reducedMotion="user" in root layout
- Add color-scheme: dark, theme-color meta, skip-to-main-content link
- Add aria-hidden={true} to ~60+ decorative icons across 33 files
- Add aria-label to all icon-only buttons (logout, star rating, play, delete, etc.)
- Replace transition-all with specific properties (11 files)
- Add motion-safe: prefix for CSS hover transforms and animate-pulse
- Add prefers-reduced-motion: reduce override in globals.css
- Add useReducedMotion guard to status-dot pulsing animation
- Fix focus-visible styles on auth form inputs (focus → focus-visible, stronger ring)
- Add text-balance to all headings (13 files)
- Replace "..." with "…" (U+2026) in all loading/placeholder text
- Add autocomplete, spellCheck, role="alert" to auth form
- Add aria-label to Switch controls and filmography select
- Fix heading hierarchy (h3 → h2 where h2 was skipped)
- Add break-words to title overview, overscroll-contain to drawer
- Add confirmation dialog for destructive library removal
- Add group-focus-within:opacity-100 for keyboard-accessible backup actions
- Add role="radio" + aria-checked to star rating buttons
- Add aria-label to carousel region, role="img" to TMDB logo SVG
- Add text-foreground to native select for dark mode consistency
Co-Authored-By: Claude Opus 4.6
---
app/(auth)/register/page.tsx | 4 +-
app/(auth)/setup/page.tsx | 26 ++++++-----
.../_components/continue-watching-card.tsx | 13 +++---
.../dashboard/_components/feed-section.tsx | 6 ++-
.../dashboard/_components/stats-display.tsx | 6 +--
.../dashboard/_components/stats-section.tsx | 4 +-
.../dashboard/_components/welcome-header.tsx | 2 +-
app/(pages)/explore/filterable-title-row.tsx | 6 ++-
app/(pages)/explore/hero-banner.tsx | 11 +++--
app/(pages)/explore/page.tsx | 8 ++--
app/(pages)/explore/title-row.tsx | 4 +-
app/(pages)/layout.tsx | 5 ++-
.../[id]/_components/filmography-grid.tsx | 9 ++--
.../people/[id]/_components/person-hero.tsx | 6 +--
app/(pages)/people/[id]/not-found.tsx | 6 ++-
.../settings/_components/account-section.tsx | 4 +-
.../_components/backup-restore-section.tsx | 9 ++--
.../_components/backup-schedule-section.tsx | 21 +++++++--
.../settings/_components/backup-section.tsx | 28 +++++++++---
.../_components/integrations-section.tsx | 5 ++-
.../_components/registration-section.tsx | 3 +-
.../settings/_components/settings-shell.tsx | 6 ++-
.../_components/system-health-section.tsx | 32 +++++++++++---
.../_components/update-check-section.tsx | 6 ++-
.../settings/_components/webhook-card.tsx | 44 +++++++++++++++----
app/(pages)/settings/page.tsx | 15 +++++--
.../titles/[id]/_components/cast-carousel.tsx | 9 ++--
.../[id]/_components/recommendations-grid.tsx | 2 +-
.../titles/[id]/_components/star-rating.tsx | 3 ++
.../titles/[id]/_components/status-button.tsx | 12 +++--
.../titles/[id]/_components/title-actions.tsx | 2 +-
.../[id]/_components/title-availability.tsx | 6 +--
.../titles/[id]/_components/title-hero.tsx | 5 ++-
.../titles/[id]/_components/title-seasons.tsx | 15 +++++--
.../[id]/_components/trailer-dialog.tsx | 5 ++-
app/globals.css | 7 +++
app/layout.tsx | 15 ++++++-
components/auth-form.tsx | 25 +++++++----
components/command-palette.tsx | 34 ++++++++++----
components/landing-page.tsx | 6 +--
components/nav-bar.tsx | 15 ++++---
components/status-dot.tsx | 16 +++++--
components/title-card.tsx | 9 ++--
components/tmdb-logo.tsx | 1 +
components/ui/button.tsx | 2 +-
components/ui/carousel.tsx | 1 +
components/ui/drawer.tsx | 2 +-
components/ui/native-select.tsx | 2 +-
components/ui/tabs.tsx | 2 +-
49 files changed, 342 insertions(+), 143 deletions(-)
diff --git a/app/(auth)/register/page.tsx b/app/(auth)/register/page.tsx
index 7084a17..bc681ee 100644
--- a/app/(auth)/register/page.tsx
+++ b/app/(auth)/register/page.tsx
@@ -22,10 +22,10 @@ export default async function RegisterPage() {
-
+
-
+
Registration Closed
diff --git a/app/(auth)/setup/page.tsx b/app/(auth)/setup/page.tsx
index 38bf302..48a710c 100644
--- a/app/(auth)/setup/page.tsx
+++ b/app/(auth)/setup/page.tsx
@@ -24,7 +24,7 @@ const steps = [
className="inline-flex items-center gap-1 font-medium text-primary underline decoration-primary/30 underline-offset-2 transition-colors hover:decoration-primary"
>
themoviedb.org
-
+
{" "}
and sign up for a free account.
>
@@ -43,7 +43,7 @@ const steps = [
className="inline-flex items-center gap-1 font-medium text-primary underline decoration-primary/30 underline-offset-2 transition-colors hover:decoration-primary"
>
Settings → API
-
+
{" "}
and request an API key. Choose “Developer” when asked. You
need the{" "}
@@ -133,10 +133,10 @@ export default function SetupPage() {
{/* Header */}
-
+
Setup required
-
+
Connect to TMDB
@@ -164,7 +164,7 @@ export default function SetupPage() {
-
{step.title}
+
{step.title}
{step.description}
@@ -188,12 +188,15 @@ export default function SetupPage() {
>
{copiedIdx === idx ? (
<>
-
+
Copied
>
) : (
<>
-
+
Copy
>
)}
@@ -217,14 +220,17 @@ export default function SetupPage() {
{configured === true ? (
-
+
TMDB API key detected
- Redirecting you to Sofa...
+ Redirecting you to Sofa…
@@ -247,7 +253,7 @@ export default function SetupPage() {
{checking ? (
<>
- Checking...
+ Checking…
>
) : (
"Check configuration"
diff --git a/app/(pages)/dashboard/_components/continue-watching-card.tsx b/app/(pages)/dashboard/_components/continue-watching-card.tsx
index 1f71dcd..1b2bf6d 100644
--- a/app/(pages)/dashboard/_components/continue-watching-card.tsx
+++ b/app/(pages)/dashboard/_components/continue-watching-card.tsx
@@ -44,18 +44,21 @@ export function ContinueWatchingCard({
alt={item.nextEpisode?.name ?? item.title.title}
width={500}
height={281}
- className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
+ className="h-full w-full object-cover motion-safe:transition-transform motion-safe:duration-300 motion-safe:group-hover:scale-105"
/>
) : (
-
+
)}
{item.nextEpisode && (
-
+
Up next
@@ -76,13 +79,13 @@ export function ContinueWatchingCard({
-
+
{progress > 0 && (
diff --git a/app/(pages)/dashboard/_components/feed-section.tsx b/app/(pages)/dashboard/_components/feed-section.tsx
index 614bda4..9bdaa38 100644
--- a/app/(pages)/dashboard/_components/feed-section.tsx
+++ b/app/(pages)/dashboard/_components/feed-section.tsx
@@ -14,8 +14,10 @@ export function FeedSection({
return (
- {icon}
-
{title}
+ {icon}
+
+ {title}
+
{children}
diff --git a/app/(pages)/dashboard/_components/stats-display.tsx b/app/(pages)/dashboard/_components/stats-display.tsx
index 98ba36e..517a00d 100644
--- a/app/(pages)/dashboard/_components/stats-display.tsx
+++ b/app/(pages)/dashboard/_components/stats-display.tsx
@@ -67,7 +67,7 @@ function StatCard({
-
+
{label}
@@ -75,7 +75,7 @@ function StatCard({
{value}
@@ -98,7 +98,7 @@ function PeriodSelector({
{periodLabels[period]}
-
+
-
+
Your library is empty
@@ -28,7 +28,7 @@ export async function StatsSection({ userId }: { userId: string }) {
Start exploring
diff --git a/app/(pages)/dashboard/_components/welcome-header.tsx b/app/(pages)/dashboard/_components/welcome-header.tsx
index 3c4691a..c94f8fa 100644
--- a/app/(pages)/dashboard/_components/welcome-header.tsx
+++ b/app/(pages)/dashboard/_components/welcome-header.tsx
@@ -1,7 +1,7 @@
export function WelcomeHeader({ name }: { name?: string | null }) {
return (
-
+
Welcome back{name ? `, ${name}` : ""}
diff --git a/app/(pages)/explore/filterable-title-row.tsx b/app/(pages)/explore/filterable-title-row.tsx
index e4c2539..bd51d9a 100644
--- a/app/(pages)/explore/filterable-title-row.tsx
+++ b/app/(pages)/explore/filterable-title-row.tsx
@@ -106,7 +106,9 @@ function FilterableTitleRowInner({
{icon}
-
{heading}
+
+ {heading}
+
{/* Genre chips */}
@@ -116,7 +118,7 @@ function FilterableTitleRowInner({
key={genre.id}
type="button"
onClick={() => toggleGenre(genre.id)}
- className={`shrink-0 rounded-full border px-3 py-1 text-xs font-medium transition-all ${
+ className={`shrink-0 rounded-full border px-3 py-1 text-xs font-medium transition-colors ${
selectedGenre === genre.id
? "border-primary bg-primary/10 text-primary"
: "border-border/50 bg-card/50 text-muted-foreground hover:border-primary/20 hover:text-foreground"
diff --git a/app/(pages)/explore/hero-banner.tsx b/app/(pages)/explore/hero-banner.tsx
index 1f1e723..74836bd 100644
--- a/app/(pages)/explore/hero-banner.tsx
+++ b/app/(pages)/explore/hero-banner.tsx
@@ -56,7 +56,10 @@ export function HeroBanner({
{voteAverage > 0 && (
-
+
{voteAverage.toFixed(1)}
)}
@@ -65,7 +68,7 @@ export function HeroBanner({
-
+
{title}
@@ -74,9 +77,9 @@ export function HeroBanner({
-
+
Add to Library
diff --git a/app/(pages)/explore/page.tsx b/app/(pages)/explore/page.tsx
index c8b1dad..4a067c7 100644
--- a/app/(pages)/explore/page.tsx
+++ b/app/(pages)/explore/page.tsx
@@ -91,7 +91,7 @@ export default async function ExplorePage() {
}
+ icon={}
items={trendingItems.slice(0, 20)}
userStatuses={userStatuses}
episodeProgress={episodeProgress}
@@ -99,7 +99,7 @@ export default async function ExplorePage() {
}
+ icon={}
mediaType="movie"
defaultItems={popularMovieItems.slice(0, 20)}
genres={movieGenres.genres}
@@ -109,7 +109,9 @@ export default async function ExplorePage() {
}
+ icon={
+
+ }
mediaType="tv"
defaultItems={popularTvItems.slice(0, 20)}
genres={tvGenres.genres}
diff --git a/app/(pages)/explore/title-row.tsx b/app/(pages)/explore/title-row.tsx
index 8ef11cd..13f237b 100644
--- a/app/(pages)/explore/title-row.tsx
+++ b/app/(pages)/explore/title-row.tsx
@@ -38,7 +38,9 @@ export function TitleRow({
{icon}
-
{heading}
+
+ {heading}
+
{/* Ambient glow */}
-
+
{children}
diff --git a/app/(pages)/people/[id]/_components/filmography-grid.tsx b/app/(pages)/people/[id]/_components/filmography-grid.tsx
index 490a10d..3efef65 100644
--- a/app/(pages)/people/[id]/_components/filmography-grid.tsx
+++ b/app/(pages)/people/[id]/_components/filmography-grid.tsx
@@ -53,8 +53,10 @@ export function FilmographyGrid({ credits }: FilmographyGridProps) {
-
-
Filmography
+
+
+ Filmography
+
({filtered.length})
@@ -63,6 +65,7 @@ export function FilmographyGrid({ credits }: FilmographyGridProps) {
diff --git a/app/(pages)/people/[id]/_components/person-hero.tsx b/app/(pages)/people/[id]/_components/person-hero.tsx
index df49f11..83e03a0 100644
--- a/app/(pages)/people/[id]/_components/person-hero.tsx
+++ b/app/(pages)/people/[id]/_components/person-hero.tsx
@@ -48,7 +48,7 @@ export function PersonHero({ person }: PersonHeroProps) {
-
+
{person.name}
@@ -61,7 +61,7 @@ export function PersonHero({ person }: PersonHeroProps) {
{person.birthday && (
-
+
{person.birthday}
{age !== null && (
@@ -72,7 +72,7 @@ export function PersonHero({ person }: PersonHeroProps) {
)}
{person.placeOfBirth && (
-
+
{person.placeOfBirth}
)}
diff --git a/app/(pages)/people/[id]/not-found.tsx b/app/(pages)/people/[id]/not-found.tsx
index 536ea85..9f1d8eb 100644
--- a/app/(pages)/people/[id]/not-found.tsx
+++ b/app/(pages)/people/[id]/not-found.tsx
@@ -3,14 +3,16 @@ import Link from "next/link";
export default function PersonNotFound() {
return (
-
Person not found
+
+ Person not found
+
The person you're looking for doesn't exist or may have been
removed.
Explore titles
diff --git a/app/(pages)/settings/_components/account-section.tsx b/app/(pages)/settings/_components/account-section.tsx
index 794888e..b5bc435 100644
--- a/app/(pages)/settings/_components/account-section.tsx
+++ b/app/(pages)/settings/_components/account-section.tsx
@@ -27,7 +27,7 @@ export function AccountSection({
return (
-
+
Account
@@ -59,7 +59,7 @@ export function AccountSection({
router.refresh();
}}
>
-
+
Sign out
diff --git a/app/(pages)/settings/_components/backup-restore-section.tsx b/app/(pages)/settings/_components/backup-restore-section.tsx
index 0f85dbd..ca827bf 100644
--- a/app/(pages)/settings/_components/backup-restore-section.tsx
+++ b/app/(pages)/settings/_components/backup-restore-section.tsx
@@ -52,7 +52,10 @@ export function BackupRestoreSection() {
-
+
Restore
@@ -115,8 +118,8 @@ export function BackupRestoreSection() {
onClick={() => fileInputRef.current?.click()}
disabled={restoring}
>
- {restoring ? : }
- {restoring ? "Restoring..." : "Upload"}
+ {restoring ? : }
+ {restoring ? "Restoring\u2026" : "Upload"}
diff --git a/app/(pages)/settings/_components/backup-schedule-section.tsx b/app/(pages)/settings/_components/backup-schedule-section.tsx
index 5f6c138..072865f 100644
--- a/app/(pages)/settings/_components/backup-schedule-section.tsx
+++ b/app/(pages)/settings/_components/backup-schedule-section.tsx
@@ -147,7 +147,10 @@ function BackupScheduleInner() {
-
+
Backup schedule
@@ -163,7 +166,10 @@ function BackupScheduleInner() {
{maxRetention === 0
? "unlimited"
: `last ${maxRetention}`}
-
+
@@ -246,7 +253,10 @@ function BackupScheduleInner() {
{DAYS_OF_WEEK[dow]}
-
+
+
-
+
Database backups
@@ -111,8 +114,12 @@ export function BackupSection({
@@ -139,11 +146,17 @@ export function BackupSection({
}
>
{backup.source === "scheduled" ? (
-
+
) : backup.source === "pre-restore" ? (
-
+
) : (
-
+
)}
@@ -172,7 +185,7 @@ export function BackupSection({
-
+
diff --git a/app/(pages)/settings/_components/integrations-section.tsx b/app/(pages)/settings/_components/integrations-section.tsx
index 2bbefb6..5a5df32 100644
--- a/app/(pages)/settings/_components/integrations-section.tsx
+++ b/app/(pages)/settings/_components/integrations-section.tsx
@@ -15,7 +15,10 @@ export function IntegrationsSection({
return (
-
+
Integrations
diff --git a/app/(pages)/settings/_components/registration-section.tsx b/app/(pages)/settings/_components/registration-section.tsx
index b155e94..57d71cd 100644
--- a/app/(pages)/settings/_components/registration-section.tsx
+++ b/app/(pages)/settings/_components/registration-section.tsx
@@ -37,7 +37,7 @@ export function RegistrationSection({
-
+
Open registration
@@ -50,6 +50,7 @@ export function RegistrationSection({
checked={registrationOpen}
onCheckedChange={handleToggle}
disabled={toggling}
+ aria-label="Toggle open registration"
/>
diff --git a/app/(pages)/settings/_components/settings-shell.tsx b/app/(pages)/settings/_components/settings-shell.tsx
index 871775d..9d9e2f0 100644
--- a/app/(pages)/settings/_components/settings-shell.tsx
+++ b/app/(pages)/settings/_components/settings-shell.tsx
@@ -32,8 +32,10 @@ export function SettingsShell({
>
-
-
Settings
+
+
+ Settings
+
Manage your account and preferences
diff --git a/app/(pages)/settings/_components/system-health-section.tsx b/app/(pages)/settings/_components/system-health-section.tsx
index 1528b7d..e8bf138 100644
--- a/app/(pages)/settings/_components/system-health-section.tsx
+++ b/app/(pages)/settings/_components/system-health-section.tsx
@@ -149,7 +149,10 @@ export function SystemHealthCards() {
-
+
Health status
@@ -164,6 +167,7 @@ export function SystemHealthCards() {