diff --git a/canvas/src/components/TermsGate.tsx b/canvas/src/components/TermsGate.tsx index cc32b9a6..e165ba3d 100644 --- a/canvas/src/components/TermsGate.tsx +++ b/canvas/src/components/TermsGate.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { PLATFORM_URL } from "@/lib/api"; // TermsGate blocks the page it wraps until the user has accepted the @@ -73,39 +73,72 @@ export function TermsGate({ children }: { children: React.ReactNode }) { } }; + // Move focus to the "I agree" button when the modal opens (WCAG 2.4.3). + // The dialog is a hard gate — no Esc dismiss — so we don't need a focus + // trap loop, just a one-shot focus move into the dialog. + const agreeButtonRef = useRef(null); + useEffect(() => { + if (status !== "pending") return; + const raf = requestAnimationFrame(() => agreeButtonRef.current?.focus()); + return () => cancelAnimationFrame(raf); + }, [status]); + return ( <> {children} {status === "pending" && ( -