Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve icon a11y #5424

Merged
merged 29 commits into from
Mar 22, 2021
Merged

Improve icon a11y #5424

merged 29 commits into from
Mar 22, 2021

Conversation

rafawendel
Copy link
Member

@rafawendel rafawendel commented Mar 12, 2021

What type of PR is this? (check all applicable)

  • Feature

Description

This aims to add accessibility to icons by:

  1. Hiding decorative icons via aria-hidden
  2. Adding visually hidden "screen-reader only" helper text where applicable
  3. Adding aria-label, aria-labelledby and aria-describedby where applicable

Ant Icons already have somewhat comprehensible labels 🎉, so only particularly relevant ones will be a11y-zed

Related Tickets & Documents

#5418, #5419, #5420, #5421

Screenshots

This PR is not supposed to result in visual changes or regressions, but I used the opportunity to fix minor ones.
(Cc. @wenbow)

Before After
image image
image image

Note for reviewers

The commit order is intended to be comprehensive to review.

@rafawendel rafawendel marked this pull request as draft March 12, 2021 19:29
@rafawendel rafawendel changed the title Added screen reader CSS Improve icon a11y Mar 15, 2021
@rafawendel rafawendel self-assigned this Mar 15, 2021
@rafawendel rafawendel marked this pull request as ready for review March 15, 2021 20:06
client/app/assets/less/inc/generics.less Outdated Show resolved Hide resolved
client/app/assets/less/inc/generics.less Outdated Show resolved Hide resolved
client/app/components/EmailSettingsWarning.jsx Outdated Show resolved Hide resolved
client/app/components/FavoritesControl.jsx Show resolved Hide resolved
}
if (alreadyInGroup) {
return (
<Tooltip title="Already selected">
<i className="fa fa-check" />
<i className="fa fa-check" aria-hidden="true" />
<span className="sr-only">Already selected</span>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like tooltips should have accessibility support by Antd rather than us adding manually to each of our uses. However it doesn't 😕, so I'm no so sure how to proceed anyway. Maybe we should have a component to make it easier to drop off once Antd starts to have accessible tooltips?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tooltips are sort of a pain I will try to handle separately. In the meantime I'm uncertain whether I should provide a minimal approach to them or wait until they can be dealt with. Probably I'll just remove these changes.

@wenbow
Copy link

wenbow commented Mar 16, 2021

The high level bullet points definitely make a lot of sense. The aria tag looks great. Wrt the antd or our design system, I will defer to our awesome engs. :)

@@ -15,7 +15,7 @@ export default function VersionInfo() {
{/* eslint-disable react/jsx-no-target-blank */}
<Link href="https://version.redash.io/" className="update-available" target="_blank" rel="noopener">
Update Available <i className="fa fa-external-link m-l-5" aria-hidden="true" />
<span className="sr-only">&#40;External link&#41;</span>
<span className="sr-only">{"(External link)"}</span>
Copy link
Member

@gabrieldutra gabrieldutra Mar 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I may have been not that clear since I also put the quotes, but what I mean is: does this need this at all? Can't it be just <span className="sr-only">(External link)</span>?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it can't. I changed back bc I thought this way was clearer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it can't? HTML/jsx wise there's nothing that holds you from writing it directly (we do it in several other places in the codebase). As for screen-readers I don't really see how having it escaped would make a difference 🤔

I'm just curious because this really looks weird to me

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason it didn't work on my initial tests but now it seems fine. Thanks for pointing this, just reverted it.

@@ -3,7 +3,7 @@ import PropTypes from "prop-types";

function BigMessage({ message, icon, children, className }) {
return (
<div className={"big-message p-15 text-center " + className}>
<div className={"big-message p-15 text-center " + className} aria-live="assertive">
Copy link
Member Author

@rafawendel rafawendel Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oriented by this and the notification guide.

<Button data-test="QueryPageHeaderMoreButton">
<EllipsisOutlinedIcon rotate={90} />
<Button data-test="QueryPageHeaderMoreButton" aria-label="More actions">
<EllipsisOutlinedIcon rotate={90} aria-label="" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be a newbie question as I'm no expert with those, but what's aria-label="" used for? Why not aria-hidden="true"?

Copy link
Member Author

@rafawendel rafawendel Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention here is to make sure Ant's aria-labels will not be displayed, so as to avoid confusion. Theoretically aria-hidden does the trick, but I wanted to override it in some cases either way. Not a lot to back me up on this, though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked with a real SR. It does the trick.

client/app/assets/less/inc/generics.less Outdated Show resolved Hide resolved
client/app/components/BigMessage.jsx Outdated Show resolved Hide resolved
client/app/components/BigMessage.jsx Outdated Show resolved Hide resolved
@@ -16,16 +15,18 @@ export default function EmailSettingsWarning({ featureName, className, mode, adm
}

const message = (
<span>
<span id="sr-mail-description">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing about the id here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is actually Ok, because it's not being replicated in a way that could end up rendering two on the screen.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, in general though, it might be best to generate ids for accessibility. It prevents us from colliding as well as relying on them in ways we shouldn't (e.g. using document.getElementById in a different component, or in a test).


return (
<div className="parameter-apply-button" data-show={!!paramCount} data-test="ParameterApplyButton">
{/* TODO: This button stays detectable even when invisible */}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, but I'd actually favor just creating Follow Up issues to track these instead of putting TODOs in the application (they are usually forsaken).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been doing both, particularly because I intend to tackle these in the near future and it helps me keeping it in mind. Are you in favor of not keeping the comments for a particular reason?

Copy link
Member

@gabrieldutra gabrieldutra Mar 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reason is that they bring little or no value. Even more when it is about new features as they always have a "room to be improved". The result is that we start to have multiple comments in the codebase that are not relevant to who's reading the code as they won't even be aware of the context 🤷‍♂️.

That said, I'm against TODO comments in the code. There are a couple of exceptions I consider for them however:

  1. When you actually want to index your files with some easily searchable term (e.g.: we used ANGULAR_REMOVE_ME to track any of the still dependent leftovers while migrating away from it)
  2. When the TODO is actually relevant to anyone that will read the code (I can't think about an example for this now 😕)

Anyway, this is more of an opinion, maybe someone else has a different thought...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM!

client/app/pages/dashboards/components/DashboardHeader.jsx Outdated Show resolved Hide resolved

function BigMessage({ message, icon, children, className }) {
const messageId = uniqueId("bm-message");
Copy link
Member

@gabrieldutra gabrieldutra Mar 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one has to be in a useRef:

const messageIdRef = useRef();
if (!messageIdRef.current) {
    messageIdRef.current = uniqueId("bm-message");
}

otherwise it will regenerate the id on every re-render.

Also in the other component (EmailSettingsWarning) as it is not specific to a a page (it could be used anywhere), maybe it's safer to do the same thing in there too?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just:

Suggested change
const messageId = uniqueId("bm-message");
const { current: messageId } = useRef(uniqueId("bm-message"));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This partially solves the thing as it will execute uniqueId regardless. Problem with this is that iirc, Lodash uses some sort of global var to track and sum up the ids. (We would end up with some possibly big ones 😝)

Copy link
Member

@gabrieldutra gabrieldutra Mar 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In brief, that was to follow this as that will avoid the component id to be updated, but not the Lodash reference to the "next id"

Copy link

@thielium thielium left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from the messageId ref conversation, just nits! Way to go! This is a huge step forward!


function BigMessage({ message, icon, children, className }) {
const messageId = uniqueId("bm-message");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just:

Suggested change
const messageId = uniqueId("bm-message");
const { current: messageId } = useRef(uniqueId("bm-message"));

aria-live="assertive"
aria-relevant="additions removals">
<h3 className="m-t-0 m-b-0" aria-labelledby={messageId}>
<i className={"fa " + icon} aria-hidden="true" />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do or not: could use classnames, not a big deal.

@@ -16,16 +15,18 @@ export default function EmailSettingsWarning({ featureName, className, mode, adm
}

const message = (
<span>
<span id="sr-mail-description">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, in general though, it might be best to generate ids for accessibility. It prevents us from colliding as well as relying on them in ways we shouldn't (e.g. using document.getElementById in a different component, or in a test).

<>
{" "}
<i className="fa fa-external-link" style={{ marginLeft: 5 }} aria-hidden="true" />
<span className="sr-only">(opens in a new tab)</span>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like this is getting repeated, some. I don't have a great idea for abstracting it, yet, but it feels like it maybe could be.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming soon 😉

<i className={cx("zmdi zmdi-refresh", { "zmdi-hc-spin": refreshClickButtonId === 1 })} />{" "}
<i className={cx("zmdi zmdi-refresh", { "zmdi-hc-spin": refreshClickButtonId === 1 })} aria-hidden="true" />
<span className="sr-only">
{refreshClickButtonId === 1 ? "Refreshing, please wait. " : "Press to refresh. "}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do or not: feels like some duplication, here -- if you feel like you can abstract it, go for it! But if you think it could create business logic changes, don't worry about it.

@@ -94,7 +94,8 @@ function NotificationTemplate({ alert, query, columnNames, resultValues, subject
data-test="CustomBody"
/>
<HelpTrigger type="ALERT_NOTIF_TEMPLATE_GUIDE" className="f-13">
<i className="fa fa-question-circle" /> Formatting guide
<i className="fa fa-question-circle" aria-hidden="true" /> Formatting guide
<span className="sr-only"> (help)</span>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) Extra space?

return lazyRef as React.MutableRefObject<T>;
}

export default useLazyRef;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer the named export export function useLazyRef(...)

return id;
}

export default useUniqueId;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing about the named export 🙂

Copy link
Member

@gabrieldutra gabrieldutra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work and patience here @rafawendel 🐳

@rafawendel rafawendel merged commit fb90b50 into master Mar 22, 2021
@rafawendel rafawendel deleted the improve-icon-a11y branch March 23, 2021 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants