accessibility: improve screen reader announcements for emoji reaction picker buttons - #7922
accessibility: improve screen reader announcements for emoji reaction picker buttons#7922UlisesMilani wants to merge 2 commits into
Conversation
| isSelected && 'module-ReactionPickerPicker__button--is-selected' | ||
| )} | ||
| onPress={onClick} | ||
| aria-label={label} |
There was a problem hiding this comment.
We actually have an API that does a better job localizing the emoji name:
aria-label={Emoji.getDisplayLabel(emoji)}I think we can get rid of the title attribute. The aria guide for toggle buttons specify that you shouldn't change the label based on the state:
Toggle button: A two-state button that can be either off (not pressed) or on (pressed). To tell assistive technologies that a button is a toggle button, specify a value for the attribute aria-pressed. For example, a button labelled mute in an audio player could indicate that sound is muted by setting the pressed state true. Important: it is critical the label on a toggle does not change when its state changes. In this example, when the pressed state is true, the label remains "Mute" so a screen reader would say something like "Mute toggle button pressed". Alternatively, if the design were to call for the button label to change from "Mute" to "Unmute," the aria-pressed attribute would not be needed.
There was a problem hiding this comment.
Thanks for the review, @jamiebuilds-signal!
- Regarding the emoji name localization:
I tested usingEmoji.getDisplayLabel(emoji)locally with screen readers (NVDA/JAWS), but the returned text labels are very simplified compared to delegating the raw Unicode character. For example, for the crying face emoji it announces just "llorar" (cry) instead of the full descriptive name "cara llorando", and for the red heart emoji it just says "corazón" (heart) instead of "corazón rojo" (red heart).
There is no need to worry about adding translation strings in our codebase for this: by passing the raw Unicode emoji character as the aria-label, the browser/OS delegates the translation directly to the screen reader's native CLDR engine. The screen reader automatically translates and pronounces the emoji based on the active system language of the user. I verified this behavior across multiple languages (including English, Spanish, French, Portuguese, and Italian) and it works flawlessly, sounding much more natural in every single one of them.
Therefore, I've updated the full emoji picker cells (in the "More" panel) to use the raw emoji Unicode character as well, so it matches the suggested reactions' behavior and gets the correct full screen reader name.
- Regarding the toggle button state:
You are completely right. I've respected the W3C ARIA toggle button guidelines and simplified the button labels to be static (always the emoji itself), lettingaria-pressed={isSelected}convey the selection/active state instead of changing the label dynamically.
I also added a check so that the long-press skin tone description ("press and hold to select skin tone") is only announced for emojis that actually support skin tone variants.
I've pushed these updates to this branch. Let me know if you have any further feedback!
There was a problem hiding this comment.
Yeah, there are definitely many cases where the official unicode title is a better accessible description of the emoji, although there are also cases where the official title doesn't really match how emojis get used by people or are wordy to the point of being disruptive in the middle of text.
There is also a gap between the languages supported by screen readers (and operating systems or the Unicode CLDR) and the languages Signal supports. Which you aren't going to see as much only looking at very broadly supported locales like English/Spanish/French/etc). Language support can also vary on different operating systems, and where possible we try to rely on our own translations because the app locale can differ from the system locale.
I am going to bring it up with our localization team to see if we could review and get accessible descriptions for every emoji. That is gonna take some time though.
In the mean time, I wonder if it would be an improvement to use the short name from Emoji.getDisplayLabel() as the aria-label and provide the emoji in aria-description to fallback to whatever Unicode name is available.
<span role="img" aria-label="heart" aria-description="❤️">❤️</span>There was a problem hiding this comment.
Ha, apparently our localization team was already working on getting translations together for the CLDR annotations dataset.
ce1a708 to
3efd3e0
Compare
First time contributor checklist:
Contributor checklist:
mainbranchpnpm run readyrun passes successfully (more about tests here)Description
This PR improves accessibility and screen reader support for the emoji reaction buttons in the reaction picker.
Issues Addressed & Steps to Replicate
aria-pressed={isSelected}on the interactive button to correctly convey the active selection state of the reaction.aria-labelto the raw emoji character, which delegates emoji localization to the client's screen reader CLDR engine.role="presentation"on the inner visual<FunStaticEmoji>to avoid duplicate reading in the accessibility tree.Architectural & Accessibility Design Choices
1. Delegating Emoji Descriptions to the Client-Side Screen Reader (CLDR)
Instead of relying on custom, app-defined translations for emoji shortnames, this PR sets the
aria-labeldirectly to the raw Unicode emoji character (e.g.❤️or👍).2. Architecture Preservation & Fallback (
title ?? emoji)We preserve the app's
i18ntranslations where they are explicitly defined:const label = title ?? emojiensures that when atitleprop is explicitly provided (such as for the "Remove reaction" button which is translated viai18n('icu:Reactions--remove')), the translation system takes precedence. Raw emojis are only used as labels for the default reaction picker emoji selection where no custom localized label is supplied.3. Preventing Double Announcements (DOM/Accessibility Tree Hygiene)
To prevent the screen reader from reading the emoji twice (once from the button's
aria-labeland once from the child element content), the inner<FunStaticEmoji>element is marked withrole="presentation".Test approach