From 6289c1d04fa3301636d006dbdab45697224d062f Mon Sep 17 00:00:00 2001 From: Divyateja2709 Date: Mon, 1 Jun 2026 16:07:18 +0530 Subject: [PATCH 1/2] Improve header accessibility and styling --- .../a11y/components/Header.a11y.test.tsx | 18 ++++++++++++++ frontend/src/app/globals.css | 24 +++++++++++++++++++ frontend/src/app/layout.tsx | 5 +++- frontend/src/components/Header.tsx | 15 +++++++++++- 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/frontend/__tests__/a11y/components/Header.a11y.test.tsx b/frontend/__tests__/a11y/components/Header.a11y.test.tsx index 2e28674188..1c001334a9 100644 --- a/frontend/__tests__/a11y/components/Header.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/Header.a11y.test.tsx @@ -1,3 +1,21 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import { axe } from 'jest-axe' +import 'jest-axe/extend-expect' +import '@testing-library/jest-dom' +import Header from 'components/Header' + +describe('Header accessibility', () => { + test('has no basic a11y violations and exposes mobile toggle attributes', async () => { + const { container } = render(
) + const results = await axe(container) + expect(results).toHaveNoViolations() + + const toggle = screen.getByRole('button', { name: /open main menu|close main menu/i }) + expect(toggle).toHaveAttribute('aria-controls', 'mobile-navigation') + expect(toggle).toHaveAttribute('aria-expanded', 'false') + }) +}) import { render } from '@testing-library/react' import { axe } from 'jest-axe' import { useTheme } from 'next-themes' diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index ade45998bb..9b29a18da7 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -399,6 +399,30 @@ select:disabled, color: black; } + /* Skip link for keyboard users */ + .skip-link { + position: absolute; + left: -999px; + top: auto; + width: 1px; + height: 1px; + overflow: hidden; + z-index: 9999; + } + + .skip-link:focus, + .skip-link:active { + left: 1rem; + top: 1rem; + width: auto; + height: auto; + padding: 0.5rem 0.75rem; + background-color: #111827; /* gray-900 */ + color: #ffffff; + border-radius: 0.375rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + } + .dark .popup-content { color: white; } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 5f67f1720b..c7af10b6dd 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -71,13 +71,16 @@ export default function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} antialiased`} style={{ minHeight: '100vh' }} > + + Skip to main content +
-
{children}
+
{children}
diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index e00f0c3259..4b53359ea1 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -46,12 +46,20 @@ export default function Header({ isGitHubAuthEnabled }: { readonly isGitHubAuthE } } + const handleKeydown = (event: KeyboardEvent) => { + if (event.key === 'Escape' && mobileMenuOpen) { + setMobileMenuOpen(false) + } + } + globalThis.addEventListener('resize', handleResize) globalThis.addEventListener('click', handleOutsideClick) + globalThis.addEventListener('keydown', handleKeydown) return () => { globalThis.removeEventListener('resize', handleResize) globalThis.removeEventListener('click', handleOutsideClick) + globalThis.removeEventListener('keydown', handleKeydown) } }, [mobileMenuOpen]) @@ -139,15 +147,20 @@ export default function Header({ isGitHubAuthEnabled }: { readonly isGitHubAuthE