Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions web/src/components/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
Expand Down
12 changes: 12 additions & 0 deletions web/src/components/RegisterPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
Expand All @@ -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 ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
Expand Down
12 changes: 12 additions & 0 deletions web/src/components/ResetPasswordPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
<EyeOff className="w-5 h-5" />
Expand Down Expand Up @@ -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 ? (
<EyeOff className="w-5 h-5" />
Expand Down
4 changes: 4 additions & 0 deletions web/src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -1834,6 +1836,8 @@ export const translations = {
passwordRequired: '่ฏท่พ“ๅ…ฅๅฏ†็ ',
invalidEmail: '้‚ฎ็ฎฑๆ ผๅผไธๆญฃ็กฎ',
passwordTooShort: 'ๅฏ†็ ่‡ณๅฐ‘้œ€่ฆ6ไธชๅญ—็ฌฆ',
showPassword: 'ๆ˜พ็คบๅฏ†็ ',
hidePassword: '้š่—ๅฏ†็ ',

// Landing Page
features: 'ๅŠŸ่ƒฝ',
Expand Down
Loading