From 95bc87f5abc866493b6392bd21f7f0f00a3df1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 15 May 2026 18:38:28 +0200 Subject: [PATCH] feat(auth): wire keyboard hints and aria error wiring on login and register Bring the auth flows to parity with the rest of the v1.4.27 form sweep so screen-reader users hear the form-level error banner tied to the offending field and the mobile keyboard surfaces the right on-screen affordance per step. Login: email field flips to `inputMode="email"` + `enterKeyHint="next"` and disables capitalize / spell-check; password field carries `enterKeyHint="go"`. Both inputs reference the error banner via `aria-describedby` and the banner itself gains `aria-live="polite"`. Register: same pattern across email, username, and the new-password field. The submit key on password is `"go"` to fire the create- account submit on virtual-keyboard return. --- src/app/auth/login/page.tsx | 21 ++++++++++++++++++++- src/app/auth/register/page.tsx | 26 +++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 4d1d494a3..adaefb661 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useId, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { KeyRound, Lock, Loader2 } from "lucide-react"; @@ -23,6 +23,12 @@ export default function LoginPage() { const [password, setPassword] = useState(""); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); + // v1.4.27 MB3 — explicit error-region id so the email + password + // inputs reference the banner via `aria-describedby`. Screen readers + // pair the error with the field instead of announcing it as a + // standalone alert detached from the form. + const errorId = useId(); + const errorDescriptor = error ? errorId : undefined; const { data: registrationEnabled } = useQuery({ queryKey: ["auth", "registration-status"], queryFn: async () => { @@ -176,6 +182,13 @@ export default function LoginPage() { onChange={(e) => setEmail(e.target.value)} required autoComplete="username" + inputMode="email" + enterKeyHint="next" + autoCapitalize="none" + spellCheck={false} + aria-required="true" + aria-invalid={!!error || undefined} + aria-describedby={errorDescriptor} placeholder={t("auth.emailOrUsernamePlaceholder")} /> @@ -188,6 +201,10 @@ export default function LoginPage() { onChange={(e) => setPassword(e.target.value)} required autoComplete="current-password" + enterKeyHint="go" + aria-required="true" + aria-invalid={!!error || undefined} + aria-describedby={errorDescriptor} placeholder="********" /> @@ -216,7 +233,9 @@ export default function LoginPage() { {error && ( @@ -98,6 +110,12 @@ export default function RegisterPage() { placeholder="user" required autoComplete="username" + enterKeyHint="next" + autoCapitalize="none" + spellCheck={false} + aria-required="true" + aria-invalid={!!error || undefined} + aria-describedby={errorDescriptor} minLength={3} maxLength={30} /> @@ -113,6 +131,10 @@ export default function RegisterPage() { placeholder={t("auth.passwordMinLength")} required autoComplete="new-password" + enterKeyHint="go" + aria-required="true" + aria-invalid={!!error || undefined} + aria-describedby={errorDescriptor} />

@@ -122,7 +144,9 @@ export default function RegisterPage() { {error && (