[Badge] Improve accessibility
See original GitHub issue- I have searched the issues of this repository and believe that this is not a duplicate.
Summary š”
It would be helpful to have a little more flexible control over both nodes used to compose the <Badge>
, so the badge can display the correct UI for sighted users and also render a helpful label for assistive technology.
Examples š
In my app we have a link in the primary nav that wraps badge wrapping an icon, used to indicate the total number of notifications for the current user.
Demo: https://codesandbox.io/s/material-demo-cc7rw?file=/demo.js
const notificationsCount = 1000
<a href="/notifications">
<Badge badgeContent={notificationsCount} color="primary">
<MailIcon titleAccess="notifications" />
</Badge>
</a>
But with the above markup the linkās accessible name ā used when screen readers encounter the link, for example ā is
notifications 99+
when it would ideally be
99+ notifications
One option is to change the DOM order of these nodes and put the {children}
after the <span>{displayValue}</span>
. If itās safe to assume that this is a logical DOM order for most users, and it doesnāt affect the visual layout, then that seems like a reasonable solution.
But I was hoping to have full props access to the <span>{displayValue}</span>
node, so I could find another solution like hiding it from assistive technology using aria-hidden
and moving the equivalent of displayValue
to the accessible icon title. Itās a little annoying that weād have to recreate the logic of displayValue
, but I could live with that.
Demo: https://codesandbox.io/s/material-demo-3ghl7?file=/demo.js
const notificationsCount = 1000
const max = 99;
const displayValue = notificationsCount > max ? `${max}+` : notificationsCount;
<a href="/notifications">
<Badge
badgeContent={notificationsCount}
color="primary"
displayValueProps={{ "aria-hidden": true }}
>
<MailIcon titleAccess={`${displayValue} notifications`} />
</Badge>
</a>
Unfortunately thatās not part of the <Badge>
API, so thatās not possible. And using the classes
prop wonāt help here, either, since thereās no CSS that will hide a node from assistive technology and preserve it visually, only the opposite (e.g. display: none
). So the resulting accessible label is
99+ notifications 99+
The next option I tried was wrapping the badgeContent
in an aria-hidden
node directly, but that breaks some more default functionality, like passing in my own displayValue
and specifiying that it should be hidden when the count is 0
. If it were possible for the <Badge>
component to read children
as the equivalent of innerText
and then parse it as an integer, then <span aria-hidden>8</span>
could still be parsed as the number 8
instead of a string or a component, etc.
Demo: https://codesandbox.io/s/material-demo-fxbpy?file=/demo.js
const notificationsCount = 1000
const max = 99;
const displayValue = notificationsCount > max ? `${max}+` : notificationsCount;
<a href="/notifications">
<Badge
badgeContent={<span aria-hidden>{displayValue}</span>}
color="primary"
invisible={notificationsCount === 0}
>
<MailIcon titleAccess={`${displayValue} notifications`} />
</Badge>
</a>
Finally, it all works! But it seems unnecessarily painful to get there.
I recognize there are other options, still, like
Demo: https://codesandbox.io/s/material-demo-ycehq?file=/demo.js
const notificationsCount = 1000
<a href="/notifications">
<Badge badgeContent={notificationsCount} color="primary">
<MailIcon />
</Badge>
<span className="visually-hidden">notifications</span>
</a>
But either changing the default DOM order of the two nodes or providing props access to the displayValue
both seem like reasonable enhancements, as well.
Motivation š¦
Iād love to leverage of all the great existing logic and UI built into <Badge>
, instead of re-writing big chunks of it to accomodate an accessible label.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:2
- Comments:14 (10 by maintainers)
Top GitHub Comments
Hey @eps1lon Iām sorry if Iāve come across wrong, or, on the flip side, if Iām misinterpreting your responses. It feels like this conversation has gone astray somehow. I opened the issue with the understanding it wasnāt an obvious show-stopper with a clear solution. But I thought Iād put it out there and that if the maintainers saw merit in some of the suggestions, thatād be great! And if not, thatās totally fine too.
Originally, the only suggestion I had written was that itād be helpful (and seemingly on par for other MUI components) to spread props on the
displayValue
node, so the component had a tiny bit more flexibility for consumers like myself. But then when I saw the āMotivationā section of the issue template, it seemed more useful to explain why I got to that conclusion in the first place ā describing the problem, not just one potential solution.As I said earlier, I will absolutely understand if MUI doesnāt think this is worth tackling. Itās not important to me to be right, I was just hoping to find opportunities to contribute to MUI. My teammates and I work with the framework everyday and would love to give back more where itās helpful. So I would love start out on the right foot. š
In any case, Iām still happy to answer your questions best as I understand them.
I donāt believe thereās an objective ācorrectā here when talking about strings that correspond to localized written language. If you were to ask me, āHow many comments are there on this thing?ā I would say, ā8 commentsā as opposed to ācomments 8ā. Thatās not ācorrectā per se, just more conventional, at least in English. So Iām reading the accessible badge label from the same lens.
Iām not sure how MUIās approach to localization would affect this, but my assumption is that the DOM order in this component doesnāt change in different locales and that the default locale for the app is considered English. If those things are true, it doesnāt seem like a stretch to imagine that some particular formulation(s) of English phrases are more conventional than others and thus worth aiming for in MUIās components. But I definitely donāt believe there is one ācorrectā solution here, and if that makes it more challenging to find an agreeable solution thatās totally fair.
I totally agree about the first rule of aria. And I agree that we should be deferring to AT as a rule of thumb about how to treat web content, that they should be considered the domain experts. But I think this particular case could be considered a real world shortcoming of AT thatās worth acknowledging and working around. For example, NVDA ignores the plus and equals symbols. It says āfive two sevenā when it should say āfive plus two equals seven.ā They have clearly made that choice for some reason, but it may not align with this componentās intent. Given that the screen reader is an entirely different medium and itās my assumption that MUI is not building primarily for AT, the screen reader naturally has to make some assumptions on our behalf.
Itās my understanding that the
+
character in the Badge label is intended to convey that there are more than 99 of something, or whatever the max number is. But for NVDA users (the most popular screen reader) it will always read ā99ā even when the count is greater than ā99ā. Itās obviously not intentional and annoying that we even have to think about it, but itās a small way in which we could gain more parity between sighted and non-sighted users. I think letting all users know there are more thann
of something seems to match the spirit of the+
.Iāll try my best to clarify my earlier statements. Iām sorry if it came across this way, but I donāt mean to suggest thereās a single ācorrectā position or that changing the DOM order would create a conventionallly semantic label for all users in all locales.
But if (itās a big if) MUI considers English the default locale, then
[count] [things]
seems more conventional, that more people might understand what it means. MUI doesnāt seem to be intentionally taking a stand about the accessible label by using a specific DOM order. But a given DOM order exists nonetheless, which creates accessible label by default. So if you agreed that the MUI default could better match the default locale of English, then maybe thatād be worth pursuing. I recognize, though, that itās subjective and might be safer to not take a stand at all and let individual consumers decide on accessible labels for themselves.Itās manually translatable just like any string in a codebase, but some of the common automatic machine translation services (e.g. Google Translate) wonāt reliably translate that attribute. This is one example Adrian gives in his article, where the
aria-label
ofLeft-Pointing Magnifying Glass
remains untranslated. So if there are more reliable alternatives like.visually-hidden
label text, itās nice to use them. Support for automatically translating aria-label has been improving, but itās still not perfect. And I agree with you that the first rule of aria would apply here, too.Itās not a React problem or specifically related to the
<Badge>
component, just an issue witharia-label
in general. I shared the links to explain why your suggestion about usingaria-label
text is totally valid (and appreciated!) but not applicable in my specific use case. Iād like to be able to support automatic translation services in the app Iām working in.So the idea is that you donāt handle translation at all but defer to google translate. And that approach does not work with certain naming techniques.
While I understand that concern I donāt see this as a viable restriction for us. aria-label is specified, implemented and used by authors. Are there any plans by the ARIA folks to remove this attribute since itās not translateable and if not what reasons did they give? Especially considering
aria-description
is on track to become standardized as well. Seems like theyāre doubling down on problematic attributes.Seems like
be the best approach but I would only start with
id
for now. That way you could do:For the
+
vsplus
issue a bit more research would be appreciated like existing issues, behavior in other screen readers etc. Because you can fix this right now manually with<Badge badgeContent={<React.Fragment>99<div aria-hidden>+</div><span className="visually-hidden">plus</span></React.Fragment>} />