From b7a0cb589bda1cc56b9948efa736343b987fbf60 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 15:53:25 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Improve=20accessibili?=
=?UTF-8?q?ty=20for=20password=20visibility=20toggles?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Added `aria-label` and `aria-pressed` to password visibility toggle buttons in Login, Register, and Reset Password pages.
- Added missing `showPassword` and `hidePassword` translations in English and Chinese.
- This ensures screen reader users can understand and interact with the password toggle functionality.
Co-authored-by: tinkle-community <240652709+tinkle-community@users.noreply.github.com>
---
.Jules/palette.md | 3 +++
web/src/components/LoginPage.tsx | 6 ++++++
web/src/components/RegisterPage.tsx | 12 ++++++++++++
web/src/components/ResetPasswordPage.tsx | 12 ++++++++++++
web/src/i18n/translations.ts | 4 ++++
5 files changed, 37 insertions(+)
create mode 100644 .Jules/palette.md
diff --git a/.Jules/palette.md b/.Jules/palette.md
new file mode 100644
index 0000000000..d23258ade1
--- /dev/null
+++ b/.Jules/palette.md
@@ -0,0 +1,3 @@
+## 2025-05-14 - Password Visibility Toggle Accessibility
+**Learning:** Icon-only buttons for toggling password visibility are often missing `aria-label` and `aria-pressed` states, making them unusable for screen reader users who need to verify their input.
+**Action:** Always include dynamic `aria-label` (Switching between "Show password" and "Hide password") and `aria-pressed` state for password toggles.
diff --git a/web/src/components/LoginPage.tsx b/web/src/components/LoginPage.tsx
index 115c1def0c..208e397454 100644
--- a/web/src/components/LoginPage.tsx
+++ b/web/src/components/LoginPage.tsx
@@ -334,6 +334,12 @@ export function LoginPage() {
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-600 hover:text-zinc-400 transition-colors"
+ aria-label={
+ showPassword
+ ? t('hidePassword', language)
+ : t('showPassword', language)
+ }
+ aria-pressed={showPassword}
>
{showPassword ? : }
diff --git a/web/src/components/RegisterPage.tsx b/web/src/components/RegisterPage.tsx
index cf92819c0f..88dd785391 100644
--- a/web/src/components/RegisterPage.tsx
+++ b/web/src/components/RegisterPage.tsx
@@ -248,6 +248,12 @@ export function RegisterPage() {
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-600 hover:text-zinc-400 transition-colors"
+ aria-label={
+ showPassword
+ ? t('hidePassword', language)
+ : t('showPassword', language)
+ }
+ aria-pressed={showPassword}
>
{showPassword ? : }
@@ -269,6 +275,12 @@ export function RegisterPage() {
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-600 hover:text-zinc-400 transition-colors"
+ aria-label={
+ showConfirmPassword
+ ? t('hidePassword', language)
+ : t('showPassword', language)
+ }
+ aria-pressed={showConfirmPassword}
>
{showConfirmPassword ? : }
diff --git a/web/src/components/ResetPasswordPage.tsx b/web/src/components/ResetPasswordPage.tsx
index 2504c9c8a3..7d9b74ba5c 100644
--- a/web/src/components/ResetPasswordPage.tsx
+++ b/web/src/components/ResetPasswordPage.tsx
@@ -150,6 +150,12 @@ export function ResetPasswordPage() {
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-2 w-8 h-10 flex items-center justify-center btn-icon"
style={{ color: 'var(--text-secondary)' }}
+ aria-label={
+ showPassword
+ ? t('hidePassword', language)
+ : t('showPassword', language)
+ }
+ aria-pressed={showPassword}
>
{showPassword ? (
@@ -184,6 +190,12 @@ export function ResetPasswordPage() {
}
className="absolute inset-y-0 right-2 w-8 h-10 flex items-center justify-center btn-icon"
style={{ color: 'var(--text-secondary)' }}
+ aria-label={
+ showConfirmPassword
+ ? t('hidePassword', language)
+ : t('showPassword', language)
+ }
+ aria-pressed={showConfirmPassword}
>
{showConfirmPassword ? (
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index 7d44356e76..eb96949cf9 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -667,6 +667,8 @@ export const translations = {
passwordRequired: 'Password is required',
invalidEmail: 'Invalid email format',
passwordTooShort: 'Password must be at least 6 characters',
+ showPassword: 'Show password',
+ hidePassword: 'Hide password',
// Landing Page
features: 'Features',
@@ -1834,6 +1836,8 @@ export const translations = {
passwordRequired: '请输入密码',
invalidEmail: '邮箱格式不正确',
passwordTooShort: '密码至少需要6个字符',
+ showPassword: '显示密码',
+ hidePassword: '隐藏密码',
// Landing Page
features: '功能',