Skip to content

Commit a536a0d

Browse files
refactor(ui): Improve UserButton/OrgSwitcher accessibility (#8325)
1 parent 52d310c commit a536a0d

75 files changed

Lines changed: 501 additions & 90 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changeset/user-button-a11y.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/ui': minor
3+
'@clerk/shared': patch
4+
'@clerk/localizations': patch
5+
---
6+
7+
Improve UserButton and OrganizationSwitcher accessibility. The trigger button now announces itself as a dialog trigger (`aria-haspopup="dialog"`) and the popover uses `role="dialog"` instead of `role="menu"`. UserButton and OrganizationSwitcher popovers now receive focus when opened, and actions are logically grouped with labelled `role="group"` elements for screen readers.

‎integration/tests/astro/components.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f
9393
await u.po.userButton.toHaveVisibleMenuItems([/Custom link/i, /Custom action/i, /Custom click/i]);
9494

9595
// Click custom action and check for custom page availbility
96-
await u.page.getByRole('menuitem', { name: /Custom action/i }).click();
96+
await u.page.getByRole('button', { name: /Custom action/i }).click();
9797
await u.po.userProfile.waitForUserProfileModal();
9898
await expect(u.page.getByRole('heading', { name: 'Custom Terms Page' })).toBeVisible();
9999

@@ -114,15 +114,15 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f
114114
);
115115
});
116116
});
117-
await u.page.getByRole('menuitem', { name: /Custom click/i }).click();
117+
await u.page.getByRole('button', { name: /Custom click/i }).click();
118118
expect(await eventPromise).toBe('custom_click');
119119

120120
// Trigger the popover again
121121
await u.po.userButton.toggleTrigger();
122122
await u.po.userButton.waitForPopover();
123123

124124
// Click custom link and check navigation
125-
await u.page.getByRole('menuitem', { name: /Custom link/i }).click();
125+
await u.page.getByRole('button', { name: /Custom link/i }).click();
126126
await u.page.waitForAppUrl('/user');
127127
});
128128

@@ -139,7 +139,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f
139139
await u.po.userButton.waitForPopover();
140140

141141
// First item should now be the sign out button
142-
await u.page.getByRole('menuitem').first().click();
142+
await u.page.getByRole('button').first().click();
143143
await u.po.expect.toBeSignedOut();
144144
});
145145

‎integration/tests/sign-out-smoke.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('sign out
8484
await u.page.getByRole('link', { name: 'Protected', exact: true }).click();
8585
await u.page.getByTestId('protected').waitFor();
8686
await u.page.getByRole('link', { name: 'Home' }).click();
87-
await u.page.getByRole('button', { name: 'Open user menu' }).click();
87+
await u.page.getByRole('button', { name: /Open user menu/i }).click();
8888

89-
await u.page.getByRole('menuitem', { name: 'Sign out' }).click();
89+
await u.page.getByRole('button', { name: 'Sign out' }).click();
9090
await u.po.expect.toBeSignedOut();
9191
await u.page.getByRole('link', { name: 'Protected', exact: true }).click();
9292
await u.page.waitForURL(url => url.href.includes('/sign-in?redirect_url'));

‎integration/tests/user-profile.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export default function Page() {
127127
await u.page.goToRelative('/');
128128
await u.page.waitForClerkComponentMounted();
129129

130-
await u.page.getByRole('button', { name: 'Open user menu' }).click();
130+
await u.page.getByRole('button', { name: /Open user menu/i }).click();
131131

132132
await u.page.getByText(/Manage account/).click();
133133

‎integration/tests/vue/components.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
8080
await u.po.userButton.toHaveVisibleMenuItems([/Custom link/i, /Custom page/i, /Custom action/i]);
8181

8282
// Click custom action
83-
await u.page.getByRole('menuitem', { name: /Custom action/i }).click();
83+
await u.page.getByRole('button', { name: /Custom action/i }).click();
8484
await expect(u.page.getByText('Is action clicked: true')).toBeVisible();
8585

8686
// Trigger the popover again
8787
await u.po.userButton.toggleTrigger();
8888
await u.po.userButton.waitForPopover();
8989

9090
// Click custom action and check for custom page availbility
91-
await u.page.getByRole('menuitem', { name: /Custom page/i }).click();
91+
await u.page.getByRole('button', { name: /Custom page/i }).click();
9292
await u.po.userProfile.waitForUserProfileModal();
9393
await expect(u.page.getByRole('heading', { name: 'Custom Terms Page' })).toBeVisible();
9494

@@ -98,7 +98,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
9898
await u.po.userButton.waitForPopover();
9999

100100
// Click custom link and check navigation
101-
await u.page.getByRole('menuitem', { name: /Custom link/i }).click();
101+
await u.page.getByRole('button', { name: /Custom link/i }).click();
102102
await u.page.waitForAppUrl('/profile');
103103
});
104104

@@ -115,7 +115,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
115115
await u.po.userButton.waitForPopover();
116116

117117
// First item should now be the sign out button
118-
await u.page.getByRole('menuitem').first().click();
118+
await u.page.getByRole('button').first().click();
119119
await u.po.expect.toBeSignedOut();
120120
});
121121

@@ -132,7 +132,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
132132
// Open UserProfile modal through UserButton
133133
await u.po.userButton.toggleTrigger();
134134
await u.po.userButton.waitForPopover();
135-
await u.page.getByRole('menuitem', { name: /Manage account/i }).click();
135+
await u.page.getByRole('button', { name: /Manage account/i }).click();
136136
await u.po.userProfile.waitForUserProfileModal();
137137

138138
// Verify custom pages and links are visible in the UserProfile

‎packages/localizations/src/ar-SA.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,6 +1728,9 @@ export const arSA: LocalizationResource = {
17281728
action__openUserMenu: 'فتح قائمة المستخدم',
17291729
action__signOut: 'تسجيل الخروج',
17301730
action__signOutAll: 'تسجيل الخروج من جميع الحسابات',
1731+
label__userButtonPopover: 'لوحة الحساب',
1732+
label__accountActions: 'إجراءات الحساب',
1733+
label__activeSessions: 'الجلسات النشطة',
17311734
},
17321735
userProfile: {
17331736
apiKeysPage: {

‎packages/localizations/src/be-BY.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,6 +1741,9 @@ export const beBY: LocalizationResource = {
17411741
action__openUserMenu: undefined,
17421742
action__signOut: 'Выйсці',
17431743
action__signOutAll: 'Выйсці з усіх уліковых запісаў',
1744+
label__userButtonPopover: 'Панэль уліковага запісу',
1745+
label__accountActions: 'Дзеянні з уліковым запісам',
1746+
label__activeSessions: 'Актыўныя сесіі',
17441747
},
17451748
userProfile: {
17461749
apiKeysPage: {

‎packages/localizations/src/bg-BG.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,9 @@ export const bgBG: LocalizationResource = {
17331733
action__openUserMenu: undefined,
17341734
action__signOut: 'Изход',
17351735
action__signOutAll: 'Изход от всички акаунти',
1736+
label__userButtonPopover: 'Панел на акаунта',
1737+
label__accountActions: 'Действия с акаунта',
1738+
label__activeSessions: 'Активни сесии',
17361739
},
17371740
userProfile: {
17381741
apiKeysPage: {

‎packages/localizations/src/bn-IN.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,9 @@ export const bnIN: LocalizationResource = {
17591759
action__openUserMenu: 'ব্যবহারকারী মেনু খুলুন',
17601760
action__signOut: 'সাইন আউট',
17611761
action__signOutAll: 'সব অ্যাকাউন্ট থেকে সাইন আউট করুন',
1762+
label__userButtonPopover: 'অ্যাকাউন্ট প্যানেল',
1763+
label__accountActions: 'অ্যাকাউন্ট অ্যাকশন',
1764+
label__activeSessions: 'সক্রিয় সেশন',
17621765
},
17631766
userProfile: {
17641767
apiKeysPage: {

‎packages/localizations/src/ca-ES.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,6 +1747,9 @@ export const caES: LocalizationResource = {
17471747
action__openUserMenu: "Obre el menú d'usuari",
17481748
action__signOut: 'Tanca sessió',
17491749
action__signOutAll: 'Tanca sessió de tots els comptes',
1750+
label__userButtonPopover: 'Tauler del compte',
1751+
label__accountActions: 'Accions del compte',
1752+
label__activeSessions: 'Sessions actives',
17501753
},
17511754
userProfile: {
17521755
apiKeysPage: {

0 commit comments

Comments
 (0)