Skip to content

Commit 6dcc1a6

Browse files
authored
fix: accessibility improvements login(https://wearezeta.atlassian.net/browse/WPB-20819) (#19714)
* fix: make password toggle button accessible and localised(WPB-21228) * fix: translate type * fix: Login - New view is not announced * fix: make back button accessible WPB-21466 * fix: make external link 2fa accessible(WPB-21279) * fix: password toggle button text alternative(WPB-21228) * fix: make verify account header focusable using screenkey and login subheading style adjustments * fix: add toggle password show/hide label * fix: address review comments * chore: bump core packages * fix: pipeline issues * fix: PR comments * fix: pipeline issues
1 parent d70f2fa commit 6dcc1a6

File tree

21 files changed

+201
-58
lines changed

21 files changed

+201
-58
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@
237237
"xml2js": "0.5.0",
238238
"@stablelib/utf8": "1.0.2",
239239
"[email protected]": "patch:dexie-encrypted@npm%3A2.0.0#./.yarn/patches/dexie-encrypted-npm-2.0.0-eb61eb5975.patch",
240-
"axios": "^1.9.0"
240+
"axios": "^1.9.0",
241+
"js-yaml": "^4.1.0"
241242
},
242243
"version": "0.27.0",
243244
"packageManager": "[email protected]"

src/i18n/en-US.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@
270270
"authLoginTitle": "Log in",
271271
"authPlaceholderEmail": "Email",
272272
"authPlaceholderPassword": "Password",
273+
"showTogglePasswordLabel": "Show password",
274+
"hideTogglePasswordLabel": "Hide password",
273275
"authPostedResend": "Resend to {email}",
274276
"authPostedResendAction": "No email showing up?",
275277
"authPostedResendDetail": "Check your email inbox and follow the instructions.",
@@ -1969,6 +1971,8 @@
19691971
"verify.headline": "You’ve got mail",
19701972
"verify.resendCode": "Resend code",
19711973
"verify.subhead": "Enter the six-digit verification code we sent to{newline}{email}",
1974+
"verify.codeLabel": "Six-digit code",
1975+
"verify.codePlaceholder": "Input field, enter digit",
19721976
"videoCallMenuMoreAddReaction": "Add reaction",
19731977
"videoCallMenuMoreAudioSettings": "Audio Settings",
19741978
"videoCallMenuMoreChangeView": "Change view",

src/script/auth/component/AccountForm.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ const AccountFormComponent = ({
248248
placeholder={t('accountForm.passwordPlaceholder')}
249249
pattern={ValidationUtil.getNewPasswordPattern(Config.getConfig().NEW_PASSWORD_MINIMUM_LENGTH)}
250250
data-uie-name="enter-password"
251+
showTogglePasswordLabel={t('showTogglePasswordLabel')}
252+
hideTogglePasswordLabel={t('hideTogglePasswordLabel')}
251253
/>
252254
<Text muted css={styles.passwordInfo(!!validationErrors.length)} data-uie-name="element-password-help">
253255
{t('accountForm.passwordHelp', {minPasswordLength: String(Config.getConfig().NEW_PASSWORD_MINIMUM_LENGTH)})}
@@ -269,6 +271,8 @@ const AccountFormComponent = ({
269271
placeholder={t('accountForm.confirmPasswordPlaceholder')}
270272
pattern={`^${registrationData.password?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`}
271273
data-uie-name="enter-confirm-password"
274+
showTogglePasswordLabel={t('showTogglePasswordLabel')}
275+
hideTogglePasswordLabel={t('hideTogglePasswordLabel')}
272276
/>
273277

274278
<Exception errors={[authError, ...validationErrors]} />

src/script/auth/component/BackButton.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ export const BackButton = () => {
2727
const navigate = useNavigate();
2828

2929
return (
30-
<ArrowIcon
30+
<button
31+
type="button"
3132
onClick={() => navigate(-1)}
32-
direction="left"
33-
data-uie-name="go-index"
3433
aria-label={t('createPersonalAccount.goBack')}
35-
color={COLOR.TEXT}
36-
/>
34+
data-uie-name="go-index"
35+
css={{background: 'none', border: 'none', cursor: 'pointer'}}
36+
>
37+
<ArrowIcon direction="left" aria-hidden="true" focusable="false" color={COLOR.TEXT} />
38+
</button>
3739
);
3840
};

src/script/auth/component/ClientItem.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ const ClientItem = ({selected, onClientRemoval, onClick, client, clientError, re
311311
placeholder={t('clientItem.passwordPlaceholder')}
312312
required
313313
type="password"
314+
showTogglePasswordLabel={t('showTogglePasswordLabel')}
315+
hideTogglePasswordLabel={t('hideTogglePasswordLabel')}
314316
value={password}
315317
/>
316318
</FlexBox>

src/script/auth/component/JoinGuestLinkPasswordModal.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ const JoinGuestLinkPasswordModal = ({
8383
id="guest_link_join_password"
8484
className="modal__input"
8585
type="password"
86+
showTogglePasswordLabel={t('showTogglePasswordLabel')}
87+
hideTogglePasswordLabel={t('hideTogglePasswordLabel')}
8688
autoComplete="off"
8789
value={passwordValue}
8890
onChange={event => setPasswordValue(event.currentTarget.value)}

src/script/auth/component/LoginForm.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ const LoginForm = ({isFetching, onSubmit}: LoginFormProps) => {
140140
pattern={'.{1,1024}'}
141141
required
142142
data-uie-name="enter-password"
143+
showTogglePasswordLabel={t('showTogglePasswordLabel')}
144+
hideTogglePasswordLabel={t('hideTogglePasswordLabel')}
143145
/>
144146

145147
{isFetching ? (
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2025 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
import {useRouteA11y} from '../hooks/useRouteA11y';
21+
22+
export const RouteA11y: React.FC = (): null => {
23+
useRouteA11y();
24+
return null;
25+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2025 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
import {useEffect} from 'react';
21+
22+
import {useLocation} from 'react-router-dom';
23+
24+
export function useRouteA11y(screenKey?: string) {
25+
const location = useLocation();
26+
27+
useEffect(() => {
28+
const focusTarget: HTMLElement | null =
29+
document.querySelector<HTMLElement>('[data-page-title]') ||
30+
document.querySelector<HTMLElement>('main,[role="main"]') ||
31+
document.querySelector<HTMLElement>('h1');
32+
33+
if (!focusTarget) {
34+
return;
35+
}
36+
37+
// scroll to top on each route change
38+
window.scrollTo({top: 0, left: 0});
39+
40+
const element = focusTarget;
41+
element.setAttribute('tabindex', '-1');
42+
element.classList.add('sr-only-focus');
43+
element.focus({preventScroll: true});
44+
45+
// remove tabindex after blur
46+
const handleBlur = () => {
47+
element.classList.remove('sr-only-focus');
48+
element.removeAttribute('tabindex');
49+
element.removeEventListener('blur', handleBlur);
50+
};
51+
element.addEventListener('blur', handleBlur);
52+
}, [location.key, screenKey]);
53+
}

src/script/auth/page/CreatePersonalAccount.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ export const CreatePersonalAccount = () => {
5151
<div css={styles.backButtonContainer}>
5252
<BackButton />
5353
</div>
54-
<p css={styles.header}>{t('createPersonalAccount.headLine')}</p>
54+
<p css={styles.header} role="heading" aria-level={1} data-page-title tabIndex={-1}>
55+
{t('createPersonalAccount.headLine')}
56+
</p>
5557
<AccountForm onSubmit={onSubmit} />
5658
<p css={styles.footer}>{t('createPersonalAccount.subHeader')}</p>
5759
<a css={styles.teamCreateButton} href={EXTERNAL_ROUTE.WIRE_TEAMS_SIGNUP} target="_blank" rel="noreferrer">

0 commit comments

Comments
 (0)