diff --git a/.changeset/user-button-a11y.md b/.changeset/user-button-a11y.md
new file mode 100644
index 00000000000..39b9a4cb99c
--- /dev/null
+++ b/.changeset/user-button-a11y.md
@@ -0,0 +1,7 @@
+---
+'@clerk/ui': patch
+'@clerk/shared': patch
+'@clerk/localizations': patch
+---
+
+Fix UserButton popover accessibility: use `role="dialog"` with grouped actions instead of `role="menu"` with `menuitem` children, fix focus management via floating-ui's interaction system, and add identity-first trigger labels
diff --git a/integration/tests/astro/components.test.ts b/integration/tests/astro/components.test.ts
index 4919fa96ec8..17f87251d2e 100644
--- a/integration/tests/astro/components.test.ts
+++ b/integration/tests/astro/components.test.ts
@@ -93,7 +93,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f
await u.po.userButton.toHaveVisibleMenuItems([/Custom link/i, /Custom action/i, /Custom click/i]);
// Click custom action and check for custom page availbility
- await u.page.getByRole('menuitem', { name: /Custom action/i }).click();
+ await u.page.getByRole('button', { name: /Custom action/i }).click();
await u.po.userProfile.waitForUserProfileModal();
await expect(u.page.getByRole('heading', { name: 'Custom Terms Page' })).toBeVisible();
@@ -114,7 +114,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f
);
});
});
- await u.page.getByRole('menuitem', { name: /Custom click/i }).click();
+ await u.page.getByRole('button', { name: /Custom click/i }).click();
expect(await eventPromise).toBe('custom_click');
// Trigger the popover again
@@ -122,7 +122,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f
await u.po.userButton.waitForPopover();
// Click custom link and check navigation
- await u.page.getByRole('menuitem', { name: /Custom link/i }).click();
+ await u.page.getByRole('button', { name: /Custom link/i }).click();
await u.page.waitForAppUrl('/user');
});
@@ -139,7 +139,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f
await u.po.userButton.waitForPopover();
// First item should now be the sign out button
- await u.page.getByRole('menuitem').first().click();
+ await u.page.getByRole('button').first().click();
await u.po.expect.toBeSignedOut();
});
diff --git a/integration/tests/sign-out-smoke.test.ts b/integration/tests/sign-out-smoke.test.ts
index 6b040080bd5..df5624c3fe4 100644
--- a/integration/tests/sign-out-smoke.test.ts
+++ b/integration/tests/sign-out-smoke.test.ts
@@ -86,7 +86,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('sign out
await u.page.getByRole('link', { name: 'Home' }).click();
await u.page.getByRole('button', { name: 'Open user menu' }).click();
- await u.page.getByRole('menuitem', { name: 'Sign out' }).click();
+ await u.page.getByRole('button', { name: 'Sign out' }).click();
await u.po.expect.toBeSignedOut();
await u.page.getByRole('link', { name: 'Protected', exact: true }).click();
await u.page.waitForURL(url => url.href.includes('/sign-in?redirect_url'));
diff --git a/integration/tests/vue/components.test.ts b/integration/tests/vue/components.test.ts
index c7966a53d34..7e3b9844f18 100644
--- a/integration/tests/vue/components.test.ts
+++ b/integration/tests/vue/components.test.ts
@@ -80,7 +80,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
await u.po.userButton.toHaveVisibleMenuItems([/Custom link/i, /Custom page/i, /Custom action/i]);
// Click custom action
- await u.page.getByRole('menuitem', { name: /Custom action/i }).click();
+ await u.page.getByRole('button', { name: /Custom action/i }).click();
await expect(u.page.getByText('Is action clicked: true')).toBeVisible();
// Trigger the popover again
@@ -88,7 +88,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
await u.po.userButton.waitForPopover();
// Click custom action and check for custom page availbility
- await u.page.getByRole('menuitem', { name: /Custom page/i }).click();
+ await u.page.getByRole('button', { name: /Custom page/i }).click();
await u.po.userProfile.waitForUserProfileModal();
await expect(u.page.getByRole('heading', { name: 'Custom Terms Page' })).toBeVisible();
@@ -98,7 +98,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
await u.po.userButton.waitForPopover();
// Click custom link and check navigation
- await u.page.getByRole('menuitem', { name: /Custom link/i }).click();
+ await u.page.getByRole('button', { name: /Custom link/i }).click();
await u.page.waitForAppUrl('/profile');
});
@@ -115,7 +115,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
await u.po.userButton.waitForPopover();
// First item should now be the sign out button
- await u.page.getByRole('menuitem').first().click();
+ await u.page.getByRole('button').first().click();
await u.po.expect.toBeSignedOut();
});
@@ -132,7 +132,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
// Open UserProfile modal through UserButton
await u.po.userButton.toggleTrigger();
await u.po.userButton.waitForPopover();
- await u.page.getByRole('menuitem', { name: /Manage account/i }).click();
+ await u.page.getByRole('button', { name: /Manage account/i }).click();
await u.po.userProfile.waitForUserProfileModal();
// Verify custom pages and links are visible in the UserProfile
diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index a9a83381088..6dfac4082ef 100644
--- a/packages/localizations/src/en-US.ts
+++ b/packages/localizations/src/en-US.ts
@@ -1454,11 +1454,14 @@ export const enUS: LocalizationResource = {
},
userButton: {
action__addAccount: 'Add account',
- action__closeUserMenu: 'Close user menu',
+ action__closeUserMenu: '{{name}} - Close account panel',
action__manageAccount: 'Manage account',
- action__openUserMenu: 'Open user menu',
+ action__openUserMenu: '{{name}} - Open account panel',
action__signOut: 'Sign out',
action__signOutAll: 'Sign out of all accounts',
+ label__userButtonPopover: 'Account panel',
+ label__accountActions: 'Account actions',
+ label__activeSessions: 'Active sessions',
},
userProfile: {
apiKeysPage: {
diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts
index db7df988428..9077f4c7d5d 100644
--- a/packages/shared/src/types/localization.ts
+++ b/packages/shared/src/types/localization.ts
@@ -995,8 +995,11 @@ export type __internal_LocalizationResource = {
action__signOut: LocalizationValue;
action__signOutAll: LocalizationValue;
action__addAccount: LocalizationValue;
- action__openUserMenu: LocalizationValue;
- action__closeUserMenu: LocalizationValue;
+ action__openUserMenu: LocalizationValue<'name'>;
+ action__closeUserMenu: LocalizationValue<'name'>;
+ label__userButtonPopover?: LocalizationValue;
+ label__accountActions?: LocalizationValue;
+ label__activeSessions?: LocalizationValue;
};
organizationSwitcher: {
personalWorkspace: LocalizationValue;
diff --git a/packages/testing/src/playwright/unstable/page-objects/userButton.ts b/packages/testing/src/playwright/unstable/page-objects/userButton.ts
index 69804cc4ef3..e64acd36edf 100644
--- a/packages/testing/src/playwright/unstable/page-objects/userButton.ts
+++ b/packages/testing/src/playwright/unstable/page-objects/userButton.ts
@@ -23,14 +23,14 @@ export const createUserButtonPageObject = (testArgs: { page: EnhancedPage }) =>
menuItems = [menuItems];
}
for (const menuItem of menuItems) {
- await expect(page.getByRole('menuitem', { name: menuItem })).toBeVisible();
+ await expect(page.getByRole('button', { name: menuItem })).toBeVisible();
}
},
triggerSignOut: () => {
- return page.getByRole('menuitem', { name: /Sign out$/i }).click();
+ return page.getByRole('button', { name: /Sign out$/i }).click();
},
triggerManageAccount: () => {
- return page.getByRole('menuitem', { name: /Manage account/i }).click();
+ return page.getByRole('button', { name: /Manage account/i }).click();
},
switchAccount: (emailAddress: string) => {
return page.getByText(emailAddress).click();
diff --git a/packages/ui/src/components/OrganizationList/OrganizationListPage.tsx b/packages/ui/src/components/OrganizationList/OrganizationListPage.tsx
index db8937232d2..0efb4ec7e23 100644
--- a/packages/ui/src/components/OrganizationList/OrganizationListPage.tsx
+++ b/packages/ui/src/components/OrganizationList/OrganizationListPage.tsx
@@ -155,7 +155,7 @@ export const OrganizationListPageList = (props: { onCreateOrganizationClick: ()
({ margin: `${t.space.$none} ${t.space.$5}` })}>{card.error}
-
+
{(userMemberships.count || 0) > 0 &&
userMemberships.data?.map(inv => {
diff --git a/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx b/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx
index 78356967931..a355535300b 100644
--- a/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx
+++ b/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx
@@ -31,7 +31,7 @@ describe('OrganizationList', () => {
expect(queryByText('to continue to TestApp')).toBeInTheDocument();
//
expect(queryByText('Personal account')).toBeInTheDocument();
- expect(queryByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument();
+ expect(queryByRole('button', { name: 'Create organization' })).toBeInTheDocument();
});
});
@@ -78,7 +78,7 @@ describe('OrganizationList', () => {
// Display membership
expect(queryByText('Org1')).toBeInTheDocument();
- expect(queryByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument();
+ expect(queryByRole('button', { name: 'Create organization' })).toBeInTheDocument();
});
});
@@ -236,7 +236,7 @@ describe('OrganizationList', () => {
expect(queryByRole('button', { name: 'Upload' })).not.toBeInTheDocument();
expect(queryByLabelText(/name/i)).not.toBeInTheDocument();
expect(queryByLabelText(/slug/i)).not.toBeInTheDocument();
- await userEvent.click(getByRole('menuitem', { name: 'Create organization' }));
+ await userEvent.click(getByRole('button', { name: 'Create organization' }));
// Header
expect(queryByRole('heading', { name: /Create organization/i })).toBeInTheDocument();
// Form fields of CreateOrganizationForm
@@ -266,9 +266,9 @@ describe('OrganizationList', () => {
},
);
await waitFor(async () =>
- expect(await findByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(),
+ expect(await findByRole('button', { name: 'Create organization' })).toBeInTheDocument(),
);
- await userEvent.click(getByRole('menuitem', { name: 'Create organization' }));
+ await userEvent.click(getByRole('button', { name: 'Create organization' }));
await waitFor(async () => expect(await findByLabelText(/name/i)).toBeInTheDocument());
await userEvent.type(getByLabelText(/name/i), 'new org');
await userEvent.click(getByRole('button', { name: /create organization/i }));
@@ -290,9 +290,9 @@ describe('OrganizationList', () => {
const { findByRole, getByRole, userEvent, queryByLabelText } = render(, { wrapper });
await waitFor(async () =>
- expect(await findByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(),
+ expect(await findByRole('button', { name: 'Create organization' })).toBeInTheDocument(),
);
- await userEvent.click(getByRole('menuitem', { name: 'Create organization' }));
+ await userEvent.click(getByRole('button', { name: 'Create organization' }));
expect(queryByLabelText(/Name/i)).toBeInTheDocument();
expect(queryByLabelText(/Slug/i)).not.toBeInTheDocument();
});
@@ -310,9 +310,9 @@ describe('OrganizationList', () => {
const { findByRole, getByRole, userEvent, queryByLabelText } = render(, { wrapper });
await waitFor(async () =>
- expect(await findByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(),
+ expect(await findByRole('button', { name: 'Create organization' })).toBeInTheDocument(),
);
- await userEvent.click(getByRole('menuitem', { name: 'Create organization' }));
+ await userEvent.click(getByRole('button', { name: 'Create organization' }));
expect(queryByLabelText(/Name/i)).toBeInTheDocument();
expect(queryByLabelText(/Slug/i)).toBeInTheDocument();
});
@@ -445,9 +445,9 @@ describe('OrganizationList', () => {
fixtures.clerk.setActive.mockReturnValue(Promise.resolve());
await waitFor(async () =>
- expect(await findByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(),
+ expect(await findByRole('button', { name: 'Create organization' })).toBeInTheDocument(),
);
- await userEvent.click(getByRole('menuitem', { name: 'Create organization' }));
+ await userEvent.click(getByRole('button', { name: 'Create organization' }));
await waitFor(async () => expect(await findByLabelText(/name/i)).toBeInTheDocument());
await userEvent.type(getByLabelText(/name/i), 'new org');
await userEvent.click(getByRole('button', { name: /create organization/i }));
diff --git a/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx b/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
index 52c43d01821..2f6aeeab5c0 100644
--- a/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
+++ b/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
@@ -127,7 +127,7 @@ export const OrganizationSwitcherPopover = React.forwardRef
- {manageOrganizationButton}
+ {manageOrganizationButton}
);
@@ -142,10 +142,7 @@ export const OrganizationSwitcherPopover = React.forwardRef
-
+
{currentOrg
? selectedOrganizationPreview(currentOrg)
: !hidePersonal && (
diff --git a/packages/ui/src/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx b/packages/ui/src/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx
index 337bd35c83d..b0e75dc3529 100644
--- a/packages/ui/src/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx
+++ b/packages/ui/src/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx
@@ -180,7 +180,6 @@ const InvitationPreview = withCardStateProvider(
elementDescriptor={descriptors.organizationSwitcherPreviewButton}
icon={SwitchArrowRight}
onClick={acceptedOrganization ? () => onOrganizationClick(acceptedOrganization) : undefined}
- role='menuitem'
>
& { showBorder: boolean }) => {
const { showBorder, ...restProps } = props;
- return (
-
- );
+ return ;
};
export const SuggestionPreview = withCardStateProvider((props: OrganizationSuggestionResource) => {
diff --git a/packages/ui/src/components/OrganizationSwitcher/UserMembershipList.tsx b/packages/ui/src/components/OrganizationSwitcher/UserMembershipList.tsx
index 35f814b4782..9dd88ab1aae 100644
--- a/packages/ui/src/components/OrganizationSwitcher/UserMembershipList.tsx
+++ b/packages/ui/src/components/OrganizationSwitcher/UserMembershipList.tsx
@@ -83,7 +83,6 @@ export const UserMembershipList = (props: UserMembershipListProps) => {
elementId={descriptors.organizationSwitcherPreviewButton.setId('personal')}
icon={SwitchArrowRight}
onClick={onPersonalWorkspaceClick}
- role='menuitem'
>
{
elementId={descriptors.organizationSwitcherPreviewButton.setId('organization')}
icon={SwitchArrowRight}
onClick={() => onOrganizationClick(organization)}
- role='menuitem'
sx={t => ({
border: `0 solid ${t.colors.$borderAlpha100}`,
})}
diff --git a/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx b/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
index 8e1009061d7..ffc55f53b79 100644
--- a/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
+++ b/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
@@ -358,7 +358,7 @@ describe('OrganizationSwitcher', () => {
props.setProps({ hidePersonal: true });
const { getByRole, userEvent } = render(, { wrapper });
await userEvent.click(getByRole('button'));
- await userEvent.click(getByRole('menuitem'));
+ await userEvent.click(getByRole('button', { name: /manage/i }));
expect(fixtures.clerk.openOrganizationProfile).toHaveBeenCalled();
});
@@ -375,7 +375,7 @@ describe('OrganizationSwitcher', () => {
props.setProps({ hidePersonal: true });
const { getByRole, userEvent } = render(, { wrapper });
await userEvent.click(getByRole('button', { name: 'Open organization switcher' }));
- await userEvent.click(getByRole('menuitem', { name: 'Create organization' }));
+ await userEvent.click(getByRole('button', { name: 'Create organization' }));
expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalled();
});
@@ -391,7 +391,7 @@ describe('OrganizationSwitcher', () => {
const { getByRole, queryByLabelText, userEvent } = render(, { wrapper });
await userEvent.click(getByRole('button', { name: 'Open organization switcher' }));
- await userEvent.click(getByRole('menuitem', { name: 'Create organization' }));
+ await userEvent.click(getByRole('button', { name: 'Create organization' }));
expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalled();
expect(queryByLabelText(/Slug/i)).not.toBeInTheDocument();
});
@@ -619,7 +619,7 @@ describe('OrganizationSwitcher', () => {
);
await userEvent.click(getByRole('button'));
- const manageButton = await waitFor(() => screen.getByRole('menuitem', { name: /manage/i }));
+ const manageButton = await waitFor(() => screen.getByRole('button', { name: /manage/i }));
await userEvent.click(manageButton);
expect(fixtures.clerk.openOrganizationProfile).toHaveBeenCalledWith(expect.objectContaining({ getContainer }));
@@ -649,7 +649,7 @@ describe('OrganizationSwitcher', () => {
);
await userEvent.click(getByRole('button', { name: 'Open organization switcher' }));
- const createButton = await waitFor(() => screen.getByRole('menuitem', { name: 'Create organization' }));
+ const createButton = await waitFor(() => screen.getByRole('button', { name: 'Create organization' }));
await userEvent.click(createButton);
expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalledWith(expect.objectContaining({ getContainer }));
diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx
index 972fb3682ed..3e4aed265ba 100644
--- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx
+++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx
@@ -62,7 +62,7 @@ export const ChooseOrganizationScreen = (props: ChooseOrganizationScreenProps) =
({ margin: `${t.space.$none} ${t.space.$8}` })}>{card.error}
-
+
{(userMemberships.count || 0) > 0 &&
userMemberships.data?.map(inv => {
return (
diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx
index 6d735baffdc..779819b5665 100644
--- a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx
+++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx
@@ -51,7 +51,6 @@ export const SetupMfaStartScreen = withCardStateProvider((props: SetupMfaStartSc
)}
({
borderTopWidth: t.borderWidths.$normal,
diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SmsCodeFlowScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SmsCodeFlowScreen.tsx
index f37c899cde5..a62f99e644f 100644
--- a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SmsCodeFlowScreen.tsx
+++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SmsCodeFlowScreen.tsx
@@ -299,7 +299,6 @@ const SmsCodeScreen = withCardStateProvider((props: SmsCodeScreenProps) => {
)}
({
borderTopWidth: t.borderWidths.$normal,
diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx
index 1323ae0e5d5..21cb1714c82 100644
--- a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx
+++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx
@@ -466,7 +466,7 @@ describe('TaskSetupMFA', () => {
await findByText(/add sms code verification/i);
await findByText(/choose phone number you want to use/i);
await findByText(/\+30 691 1111111/i);
- expect(getByRole('menuitem', { name: /add phone number/i })).toBeInTheDocument();
+ expect(getByRole('button', { name: /add phone number/i })).toBeInTheDocument();
});
it('should show add phone screen when no existing phone numbers', async () => {
@@ -511,7 +511,7 @@ describe('TaskSetupMFA', () => {
await findByText(/add sms code verification/i);
await act(async () => {
- await userEvent.click(getByRole('menuitem', { name: /add phone number/i }));
+ await userEvent.click(getByRole('button', { name: /add phone number/i }));
});
await findByText(/add phone number/i);
diff --git a/packages/ui/src/components/SignIn/SignInAccountSwitcher.tsx b/packages/ui/src/components/SignIn/SignInAccountSwitcher.tsx
index 355cf79c9b0..f05028467d5 100644
--- a/packages/ui/src/components/SignIn/SignInAccountSwitcher.tsx
+++ b/packages/ui/src/components/SignIn/SignInAccountSwitcher.tsx
@@ -45,7 +45,7 @@ const SignInAccountSwitcherInternal = () => {
borderTopColor: t.colors.$borderAlpha100,
})}
>
-
+
{signedInSessions.map(s => (
{
const { handleManageAccountClicked, handleSignOutSessionClicked, handleUserProfileActionClicked, session } = props;
const { menutItems } = useUserButtonContext();
+ const { t } = useLocalizations();
const commonActionSx: ThemableCssProp = t => ({
borderTopWidth: t.borderWidths.$normal,
@@ -54,7 +55,8 @@ export const SingleSessionActions = (props: SingleSessionActionsProps) => {
return (
({
@@ -138,6 +140,7 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => {
} = props;
const { menutItems } = useUserButtonContext();
+ const { t } = useLocalizations();
const handleActionClick = async (route: MenuItem) => {
if (route?.path) {
@@ -164,7 +167,8 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => {
<>
{hasOnlyDefaultItems ? (
@@ -200,7 +204,8 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => {
) : (
({
@@ -267,7 +272,8 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => {
)}
({
borderTopStyle: t.borderStyles.$solid,
borderTopWidth: t.borderWidths.$normal,
@@ -279,7 +285,6 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => {
key={session.id}
icon={SwitchArrowRight}
onClick={handleSessionClicked(session)}
- role='menuitem'
>
@@ -294,6 +299,7 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => {
icon={Add}
label={localizationKeys('userButton.action__addAccount')}
onClick={handleAddAccountClicked}
+ role={undefined}
iconSx={t => ({
width: t.sizes.$9,
height: t.sizes.$6,
@@ -336,9 +342,11 @@ export const SignOutAllActions = (props: SignOutAllActionsProps) => {
sx,
actionSx,
} = props;
+ const { t } = useLocalizations();
return (
({
padding: t.space.$2,
@@ -358,6 +366,7 @@ export const SignOutAllActions = (props: SignOutAllActionsProps) => {
onClick={handleSignOutAllClicked}
variant='ghost'
colorScheme='neutral'
+ role={undefined}
sx={[
t => ({
backgroundColor: t.colors.$transparent,
diff --git a/packages/ui/src/components/UserButton/UserButtonPopover.tsx b/packages/ui/src/components/UserButton/UserButtonPopover.tsx
index 28aca51b525..6eab611bf1f 100644
--- a/packages/ui/src/components/UserButton/UserButtonPopover.tsx
+++ b/packages/ui/src/components/UserButton/UserButtonPopover.tsx
@@ -7,7 +7,7 @@ import { RootBox } from '@/ui/elements/RootBox';
import { UserPreview } from '@/ui/elements/UserPreview';
import { useEnvironment, useUserButtonContext } from '../../contexts';
-import { descriptors } from '../../customizables';
+import { descriptors, localizationKeys, useLocalizations } from '../../customizables';
import type { PropsOfComponent } from '../../styledSystem';
import { MultiSessionActions, SignOutAllActions, SingleSessionActions } from './SessionActions';
import { useMultisessionActions } from './useMultisessionActions';
@@ -22,6 +22,7 @@ export const UserButtonPopover = React.forwardRef
diff --git a/packages/ui/src/components/UserButton/UserButtonTrigger.tsx b/packages/ui/src/components/UserButton/UserButtonTrigger.tsx
index 64463e0b7a5..5a6b50b4e88 100644
--- a/packages/ui/src/components/UserButton/UserButtonTrigger.tsx
+++ b/packages/ui/src/components/UserButton/UserButtonTrigger.tsx
@@ -1,3 +1,4 @@
+import { getFullName, getIdentifier } from '@clerk/shared/internal/clerk-js/user';
import { useUser } from '@clerk/shared/react';
import { forwardRef } from 'react';
@@ -19,6 +20,7 @@ export const UserButtonTrigger = withAvatarShimmer(
const { user } = useUser();
const { showName } = useUserButtonContext();
const { t } = useLocalizations();
+ const userName = (user && (getFullName(user) || getIdentifier(user))) || '';
return (