diff --git a/frontend/src/app/[locale]/settings/page.test.tsx b/frontend/src/app/[locale]/settings/page.test.tsx
new file mode 100644
index 00000000..7cc6e680
--- /dev/null
+++ b/frontend/src/app/[locale]/settings/page.test.tsx
@@ -0,0 +1,82 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import SettingsPage from "./page";
+
+jest.mock("../../lib/session", () => ({
+ logoutUser: jest.fn(),
+}));
+
+jest.mock("../../hooks/useLogout", () => ({
+ useLogout: () => ({ logout: jest.fn() }),
+}));
+
+jest.mock("../../stores/useUserStore", () => ({
+ useUserStore: jest.fn((selector) =>
+ selector({
+ user: { id: "user1", email: "test@example.com" },
+ }),
+ ),
+ selectUser: (state: { user: { id: string; email: string } }) => state.user,
+}));
+
+jest.mock("../../stores/useWalletStore", () => ({
+ useWalletStore: jest.fn((selector) =>
+ selector({
+ address: null,
+ network: "testnet",
+ disconnect: jest.fn(),
+ }),
+ ),
+ selectWalletAddress: (state: { address: string | null }) => state.address,
+ selectWalletNetwork: (state: { network: string }) => state.network,
+}));
+
+jest.mock("../../stores/useThemeStore", () => ({
+ useThemeStore: jest.fn(() => ({
+ theme: "system",
+ setTheme: jest.fn(),
+ })),
+}));
+
+jest.mock("../../hooks/useApi", () => ({
+ useNotificationPreferences: () => ({ data: undefined, isLoading: false, error: null }),
+ useUpdateNotificationPreferences: () => ({ mutate: jest.fn(), isPending: false }),
+}));
+
+jest.mock("../../components/gamification/GamificationSettings", () => ({
+ GamificationSettings: () =>
Gamification Settings
,
+}));
+
+describe("SettingsPage section navigation", () => {
+ it("exposes the default active section via aria-selected", () => {
+ render();
+
+ expect(screen.getByRole("tab", { name: "Profile" })).toHaveAttribute("aria-selected", "true");
+ expect(screen.getByRole("tab", { name: "Wallet" })).toHaveAttribute("aria-selected", "false");
+ });
+
+ it("updates accessible state and focus when switching sections", async () => {
+ const user = userEvent.setup();
+ render();
+
+ const walletTab = screen.getByRole("tab", { name: "Wallet" });
+ await user.click(walletTab);
+
+ expect(walletTab).toHaveAttribute("aria-selected", "true");
+ expect(screen.getByRole("tab", { name: "Profile" })).toHaveAttribute("aria-selected", "false");
+ expect(document.activeElement).toBe(walletTab);
+ });
+
+ it("links each tab to its panel with aria-controls and tabpanel semantics", () => {
+ render();
+
+ const profileTab = screen.getByRole("tab", { name: "Profile" });
+ const panelId = profileTab.getAttribute("aria-controls");
+
+ expect(panelId).toBe("settings-panel-profile");
+
+ const panel = screen.getByRole("tabpanel");
+ expect(panel).toHaveAttribute("id", panelId);
+ expect(panel).toHaveAttribute("aria-labelledby", "settings-tab-profile");
+ });
+});
diff --git a/frontend/src/app/[locale]/settings/page.tsx b/frontend/src/app/[locale]/settings/page.tsx
index b4926b38..57d6c47b 100644
--- a/frontend/src/app/[locale]/settings/page.tsx
+++ b/frontend/src/app/[locale]/settings/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useEffect, useState } from "react";
+import { useEffect, useState, type KeyboardEvent } from "react";
import {
User,
Wallet,
@@ -55,6 +55,14 @@ const SECTIONS = [
type SectionId = (typeof SECTIONS)[number]["id"];
+function settingsTabId(id: SectionId) {
+ return `settings-tab-${id}`;
+}
+
+function settingsPanelId(id: SectionId) {
+ return `settings-panel-${id}`;
+}
+
// ─── Copy-to-clipboard helper ─────────────────────────────────────────────────
function CopyButton({ value }: { value: string }) {
@@ -102,15 +110,17 @@ function Toggle({
@@ -220,10 +230,11 @@ function WalletSection() {
{network?.isSupported ? "Supported" : "Unsupported"}
@@ -401,9 +412,7 @@ function NotificationsSection() {
}
/>
{phoneError && (
-
- {phoneError}
-
+ {phoneError}
)}
@@ -499,10 +508,11 @@ function SecuritySection() {
KYC Status
{user?.kycVerified ? "Verified" : "Not Verified"}
@@ -592,10 +602,11 @@ function DisplaySection() {
@@ -634,6 +645,32 @@ export default function SettingsPage() {
const [activeSection, setActiveSection] = useState
("profile");
const handleLogout = () => logoutUser("manual");
+ const activateSection = (id: SectionId) => {
+ setActiveSection(id);
+ requestAnimationFrame(() => {
+ document.getElementById(settingsTabId(id))?.focus();
+ });
+ };
+
+ const handleTabKeyDown = (event: KeyboardEvent, index: number) => {
+ let nextIndex: number | null = null;
+
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
+ nextIndex = (index + 1) % SECTIONS.length;
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
+ nextIndex = (index - 1 + SECTIONS.length) % SECTIONS.length;
+ } else if (event.key === "Home") {
+ nextIndex = 0;
+ } else if (event.key === "End") {
+ nextIndex = SECTIONS.length - 1;
+ }
+
+ if (nextIndex === null) return;
+
+ event.preventDefault();
+ activateSection(SECTIONS[nextIndex].id);
+ };
+
const renderSection = () => {
switch (activeSection) {
case "profile":
@@ -674,26 +711,50 @@ export default function SettingsPage() {
{/* Side nav */}
{/* Content */}
-
{renderSection()}
+
+ {renderSection()}
+
);