From 03de295a934da91bb44a52af8574f5bc386bd7ae Mon Sep 17 00:00:00 2001
From: owenpkent <20529132+owenpkent@users.noreply.github.com>
Date: Thu, 3 Sep 2026 17:49:16 -0600
Subject: [PATCH] fix: give the account modal an accessible name
AccountModal set aria-labelledby="rk_account_modal_title" on its dialog,
but ProfileDetails rendered its heading with its own hardcoded id,
rk_profile_title, and put that same id on the balance heading as well.
The reference never resolved, so the modal was announced as an unnamed
dialog, and the duplicated id was invalid on its own.
ProfileDetails now takes the titleId from AccountModal and renders it on
the account name only, the way ChainModal and the connect flow already
pair a dialog with its heading.
Fixes #2700
---
.../fix-account-modal-accessible-name.md | 15 +++++++
.../AccountModal/AccountModal.test.tsx | 43 +++++++++++++++++++
.../components/AccountModal/AccountModal.tsx | 1 +
.../ProfileDetails/ProfileDetails.tsx | 4 +-
4 files changed, 61 insertions(+), 2 deletions(-)
create mode 100644 .changeset/fix-account-modal-accessible-name.md
create mode 100644 packages/rainbowkit/src/components/AccountModal/AccountModal.test.tsx
diff --git a/.changeset/fix-account-modal-accessible-name.md b/.changeset/fix-account-modal-accessible-name.md
new file mode 100644
index 0000000000..683703baf8
--- /dev/null
+++ b/.changeset/fix-account-modal-accessible-name.md
@@ -0,0 +1,15 @@
+---
+"@rainbow-me/rainbowkit": patch
+---
+
+fix: give the account modal an accessible name
+
+`AccountModal` set `aria-labelledby="rk_account_modal_title"` on its dialog,
+but `ProfileDetails` rendered its heading with its own hardcoded id,
+`rk_profile_title`, and put that same id on the balance heading too. The
+reference never resolved, so screen readers announced the modal as an
+unnamed dialog, and the duplicated id was invalid on its own.
+
+`ProfileDetails` now takes the `titleId` from `AccountModal` and renders it
+on the account name only, so the dialog is announced by the connected
+address or ENS name, the way the chain and connect modals already are.
diff --git a/packages/rainbowkit/src/components/AccountModal/AccountModal.test.tsx b/packages/rainbowkit/src/components/AccountModal/AccountModal.test.tsx
new file mode 100644
index 0000000000..5383f59abf
--- /dev/null
+++ b/packages/rainbowkit/src/components/AccountModal/AccountModal.test.tsx
@@ -0,0 +1,43 @@
+import user from '@testing-library/user-event';
+import React, { Fragment } from 'react';
+import { describe, expect, it } from 'vitest';
+import { useConnect } from 'wagmi';
+import { renderWithProviders } from '../../../test/';
+import { mockedAccounts } from '../../../test/mockWallet';
+import { formatAddress } from '../ConnectButton/formatAddress';
+import { AccountModal } from './AccountModal';
+
+const AccountModalWithConnectButton = () => {
+ const { connect, connectors } = useConnect();
+
+ return (
+
+ {}} open />
+
+
+ );
+};
+
+describe('', () => {
+ it('names the dialog after the connected account', async () => {
+ const modal = renderWithProviders();
+
+ await user.click(await modal.findByTestId('rk-connect-btn'));
+
+ const dialog = await modal.findByRole('dialog');
+ const titleId = dialog.getAttribute('aria-labelledby');
+ expect(titleId).toBeTruthy();
+
+ // The id the dialog points at must exist exactly once, on the account name
+ const titles = document.querySelectorAll(`[id="${titleId}"]`);
+ expect(titles).toHaveLength(1);
+ expect(titles[0]).toHaveTextContent(formatAddress(mockedAccounts[0]));
+ expect(dialog).toHaveAccessibleName(formatAddress(mockedAccounts[0]));
+ });
+});
diff --git a/packages/rainbowkit/src/components/AccountModal/AccountModal.tsx b/packages/rainbowkit/src/components/AccountModal/AccountModal.tsx
index fa0e3babf2..75d1e37e0d 100644
--- a/packages/rainbowkit/src/components/AccountModal/AccountModal.tsx
+++ b/packages/rainbowkit/src/components/AccountModal/AccountModal.tsx
@@ -36,6 +36,7 @@ export function AccountModal({ onClose, open }: AccountModalProps) {
balance={balance}
onClose={onClose}
onDisconnect={disconnect}
+ titleId={titleId}
/>
diff --git a/packages/rainbowkit/src/components/ProfileDetails/ProfileDetails.tsx b/packages/rainbowkit/src/components/ProfileDetails/ProfileDetails.tsx
index e908d77f73..4847a300a1 100644
--- a/packages/rainbowkit/src/components/ProfileDetails/ProfileDetails.tsx
+++ b/packages/rainbowkit/src/components/ProfileDetails/ProfileDetails.tsx
@@ -26,6 +26,7 @@ interface ProfileDetailsProps {
balance: ReturnType['balance'];
onClose: () => void;
onDisconnect: () => void;
+ titleId: string;
}
export function ProfileDetails({
@@ -35,6 +36,7 @@ export function ProfileDetails({
balance,
onClose,
onDisconnect,
+ titleId,
}: ProfileDetailsProps) {
const showRecentTransactions = useContext(ShowRecentTransactionsContext);
const { i18n } = useContext(I18nContext);
@@ -65,7 +67,6 @@ export function ProfileDetails({
const displayBalance = ethBalance
? abbreviateETHBalance(Number.parseFloat(ethBalance))
: undefined;
- const titleId = 'rk_profile_title';
const mobile = isMobile();
return (
@@ -120,7 +121,6 @@ export function ProfileDetails({