From 2c9cb547e7ebe46f6ab66afaf106c6776f09efd0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:48:58 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Improve=20Contact=20f?= =?UTF-8?q?orm=20accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: Added ARIA roles to form status messages and hid decorative icons. 🎯 Why: Screen readers were missing dynamic success/error messages on form submission. ♿ Accessibility: - Added `role="alert"` and `aria-live="polite"` to success, error, and rate-limit messages. - Added `aria-hidden="true"` to decorative icons in the contact info section. Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com> --- .jules/palette.md | 3 +++ src/pages/Contact.tsx | 9 ++++++++- src/pages/__tests__/Contact.test.tsx | 30 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .jules/palette.md diff --git a/.jules/palette.md b/.jules/palette.md new file mode 100644 index 0000000..e9e6383 --- /dev/null +++ b/.jules/palette.md @@ -0,0 +1,3 @@ +## 2025-02-18 - Missing Alerts for Dynamic Status +**Learning:** The application uses `framer-motion` for dynamic feedback messages but consistently lacks `role="alert"` and `aria-live` attributes, causing screen readers to miss critical status updates. +**Action:** When auditing forms, check all `motion.div/p` elements used for feedback and add `role="alert"` and `aria-live="polite"` (or "assertive" for errors). diff --git a/src/pages/Contact.tsx b/src/pages/Contact.tsx index 708d7c8..79c91c4 100644 --- a/src/pages/Contact.tsx +++ b/src/pages/Contact.tsx @@ -210,6 +210,8 @@ export function Contact() { className={styles.success} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} + role="alert" + aria-live="polite" > {t.contact.form.success} @@ -220,6 +222,8 @@ export function Contact() { className={styles.error} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} + role="alert" + aria-live="polite" > {t.contact.form.error} @@ -230,6 +234,8 @@ export function Contact() { className={styles.error} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} + role="alert" + aria-live="polite" > {t.contact.form.rateLimit} @@ -254,6 +260,7 @@ export function Contact() { fill="none" stroke="currentColor" strokeWidth="2" + aria-hidden="true" > @@ -269,7 +276,7 @@ export function Contact() {
- +
diff --git a/src/pages/__tests__/Contact.test.tsx b/src/pages/__tests__/Contact.test.tsx index 6e9a170..60b735f 100644 --- a/src/pages/__tests__/Contact.test.tsx +++ b/src/pages/__tests__/Contact.test.tsx @@ -116,6 +116,8 @@ describe('Contact Page', () => { // Verify success message const successMessage = await screen.findByText('Message sent successfully!'); expect(successMessage).toBeTruthy(); + expect(successMessage.getAttribute('role')).toBe('alert'); + expect(successMessage.getAttribute('aria-live')).toBe('polite'); }); it('sanitizes input before sending', async () => { @@ -193,4 +195,32 @@ describe('Contact Page', () => { // EmailJS should NOT be called expect(emailjs.send).not.toHaveBeenCalled(); }); + + it('shows error message with alert role when submission fails', async () => { + // Mock failure + const sendMock = vi.mocked(emailjs.send); + sendMock.mockRejectedValueOnce(new Error('Network error')); + + render(); + + // Fill out the form + fireEvent.change(screen.getByLabelText('Name'), { target: { value: 'John Doe' } }); + fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'john@example.com' } }); + fireEvent.change(screen.getByLabelText('Subject'), { target: { value: 'Test Subject' } }); + fireEvent.change(screen.getByLabelText('Message'), { target: { value: 'Hello world' } }); + + // Submit + fireEvent.click(screen.getByRole('button', { name: 'Send Message' })); + + // Wait for submission attempt + await waitFor(() => { + expect(emailjs.send).toHaveBeenCalled(); + }); + + // Verify error message + const errorMessage = await screen.findByText('Failed to send message.'); + expect(errorMessage).toBeTruthy(); + expect(errorMessage.getAttribute('role')).toBe('alert'); + expect(errorMessage.getAttribute('aria-live')).toBe('polite'); + }); });