From 0e51452dbefdd3f5cbfa5570545272f6b8b878af Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Tue, 30 Sep 2025 13:19:47 +0530 Subject: [PATCH 1/4] Always use an accessible button with base avatar rendered inside it --- src/components/views/settings/AvatarSetting.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/components/views/settings/AvatarSetting.tsx b/src/components/views/settings/AvatarSetting.tsx index 9992e3691b2..25e72d4791f 100644 --- a/src/components/views/settings/AvatarSetting.tsx +++ b/src/components/views/settings/AvatarSetting.tsx @@ -183,20 +183,6 @@ const AvatarSetting: React.FC = ({ ); - if (avatarURL) { - avatarElement = ( - - ); - } let uploadAvatarBtn: JSX.Element | undefined; if (!disabled) { From a3d7ff5f41199e2db54fbbbd333ee9371eac3f05 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Tue, 30 Sep 2025 13:22:06 +0530 Subject: [PATCH 2/4] Rename avatarAltText to accessibleName --- .../views/room_settings/RoomProfileSettings.tsx | 2 +- src/components/views/settings/AvatarSetting.tsx | 8 ++++---- src/components/views/settings/UserProfileSettings.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/room_settings/RoomProfileSettings.tsx b/src/components/views/room_settings/RoomProfileSettings.tsx index e7674a109ac..ea727122b2d 100644 --- a/src/components/views/room_settings/RoomProfileSettings.tsx +++ b/src/components/views/room_settings/RoomProfileSettings.tsx @@ -262,7 +262,7 @@ export default class RoomProfileSettings extends React.Component ? undefined : (this.state.avatarFile ?? this.state.originalAvatarUrl ?? undefined) } - avatarAltText={_t("room_settings|general|avatar_field_label")} + avatarAccessibleName={_t("room_settings|general|avatar_field_label")} disabled={!this.state.canSetAvatar} onChange={this.onAvatarChanged} removeAvatar={canRemove ? this.removeAvatar : undefined} diff --git a/src/components/views/settings/AvatarSetting.tsx b/src/components/views/settings/AvatarSetting.tsx index 25e72d4791f..bb72b99d619 100644 --- a/src/components/views/settings/AvatarSetting.tsx +++ b/src/components/views/settings/AvatarSetting.tsx @@ -89,9 +89,9 @@ interface IProps { removeAvatar?: () => void; /** - * The alt text for the avatar + * The accessible name for the avatar, eg: "Foo's Profile Picture" */ - avatarAltText: string; + avatarAccessibleName: string; /** * String to use for computing the colour of the placeholder avatar if no avatar is set @@ -121,7 +121,7 @@ export function getFileChanged(e: React.ChangeEvent): File | n */ const AvatarSetting: React.FC = ({ avatar, - avatarAltText, + avatarAccessibleName, onChange, removeAvatar, disabled, @@ -197,7 +197,7 @@ const AvatarSetting: React.FC = ({ } const content = ( -
+
{avatarElement} {uploadAvatarBtn}
diff --git a/src/components/views/settings/UserProfileSettings.tsx b/src/components/views/settings/UserProfileSettings.tsx index 4590f948ef3..86e77b6b472 100644 --- a/src/components/views/settings/UserProfileSettings.tsx +++ b/src/components/views/settings/UserProfileSettings.tsx @@ -203,7 +203,7 @@ const UserProfileSettings: React.FC = ({
Date: Tue, 30 Sep 2025 13:23:34 +0530 Subject: [PATCH 3/4] Improve accessibility --- .../views/settings/AvatarSetting.tsx | 34 ++++++++++++------- src/i18n/strings/en_EN.json | 1 + 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/components/views/settings/AvatarSetting.tsx b/src/components/views/settings/AvatarSetting.tsx index bb72b99d619..2c8dac96d77 100644 --- a/src/components/views/settings/AvatarSetting.tsx +++ b/src/components/views/settings/AvatarSetting.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, type ReactNode, createRef, useCallback, useEffect, useState, useId } from "react"; +import React, { type JSX, type ReactNode, createRef, useCallback, useEffect, useState } from "react"; import EditIcon from "@vector-im/compound-design-tokens/assets/web/icons/edit"; import UploadIcon from "@vector-im/compound-design-tokens/assets/web/icons/share"; import DeleteIcon from "@vector-im/compound-design-tokens/assets/web/icons/delete"; @@ -147,9 +147,6 @@ const AvatarSetting: React.FC = ({ } }, [avatar]); - // Prevents ID collisions when this component is used more than once on the same page. - const a11yId = useId(); - const onFileChanged = useCallback( (e: React.ChangeEvent) => { const file = getFileChanged(e); @@ -170,17 +167,24 @@ const AvatarSetting: React.FC = ({ setMenuOpen(newOpen); }, []); - let avatarElement = ( + const avatarElement = ( {}} className="mx_AvatarSetting_avatarPlaceholder mx_AvatarSetting_avatarDisplay" - aria-labelledby={disabled ? undefined : a11yId} - // Inhibit tab stop as we have explicit upload/remove buttons - tabIndex={-1} disabled={disabled} > - + ); @@ -190,8 +194,14 @@ const AvatarSetting: React.FC = ({ mx_AvatarSetting_uploadButton_active: menuOpen, }); uploadAvatarBtn = ( -
- +
+
); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f9eab0839e5..cec1d25c0d7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2664,6 +2664,7 @@ "allow_spellcheck": "Allow spell check", "application_language": "Application language", "application_language_reload_hint": "The app will reload after selecting another language", + "avatar_open_menu": "Open avatar menu", "avatar_remove_progress": "Removing image...", "avatar_save_progress": "Uploading image...", "avatar_upload_error_text": "The file format is not supported or the image is larger than %(size)s.", From a0f96eb49db84d0fa9ae53e0099bda484e4d42b0 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Tue, 30 Sep 2025 13:26:37 +0530 Subject: [PATCH 4/4] Fix tests --- .../views/settings/AvatarSetting-test.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/test/unit-tests/components/views/settings/AvatarSetting-test.tsx b/test/unit-tests/components/views/settings/AvatarSetting-test.tsx index f6c4181b210..2d09c76232f 100644 --- a/test/unit-tests/components/views/settings/AvatarSetting-test.tsx +++ b/test/unit-tests/components/views/settings/AvatarSetting-test.tsx @@ -25,18 +25,18 @@ describe("", () => { stubClient(); }); - it("renders avatar with specified alt text", async () => { - const { queryByAltText } = render( + it("renders avatar with specified accessible name", async () => { + const { getByRole } = render( , ); - const imgElement = queryByAltText("Avatar of Peter Fox"); - expect(imgElement).toBeInTheDocument(); + const avatarButton = getByRole("button", { name: "Avatar of Peter Fox" }); + expect(avatarButton).toBeInTheDocument(); }); it("renders a file as the avatar when supplied", async () => { @@ -44,12 +44,13 @@ describe("", () => { , ); - const imgElement = await screen.findByRole("button", { name: "Avatar of Peter Fox" }); + const avatarButton = await screen.findByRole("button", { name: "Avatar of Peter Fox" }); + const imgElement = avatarButton.querySelector("img"); expect(imgElement).toBeInTheDocument(); expect(imgElement).toHaveAttribute("src", "data:image/gif;base64," + BASE64_GIF); }); @@ -63,7 +64,7 @@ describe("", () => { placeholderId="blee" placeholderName="boo" avatar="mxc://example.org/my-avatar" - avatarAltText="Avatar of Peter Fox" + avatarAccessibleName="Avatar of Peter Fox" onChange={onChange} />, ); @@ -82,7 +83,7 @@ describe("", () => { placeholderId="blee" placeholderName="boo" avatar="mxc://example.org/my-avatar" - avatarAltText="Avatar of Peter Fox" + avatarAccessibleName="Avatar of Peter Fox" onChange={onChange} />, ); @@ -102,7 +103,7 @@ describe("", () => { placeholderId="blee" placeholderName="boo" avatar="mxc://example.org/my-avatar" - avatarAltText="Avatar of Peter Fox" + avatarAccessibleName="Avatar of Peter Fox" onChange={onChange} />, );