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

Some improvements for screen readers [a11y] #715

Merged
merged 8 commits into from
Oct 5, 2017
Merged

Some improvements for screen readers [a11y] #715

merged 8 commits into from
Oct 5, 2017

Conversation

erin-doyle
Copy link
Collaborator

Doing some screen reader testing on this and identified some things that could make the reading a little more clear/helpful:

  1. The table was being interpreted as a data table versus a layout table and was reading extraneous info about the number of columns and rows in the current cell happened to be. The cell already nicely reads the full date and day of the week and therefore there's no need for the screen reader to associate the column headers with the cell.

  2. The DayPickerRangeController did not seem to be properly setting the chooseAvailableStartDate phrase after first mounting so only after picking a start date and moving to the end date would the reader nicely read "it's available" for the date options.

  3. I did also notice Issue [a11y] Disabled dates aren't actually disabled in markup #713 where unavailable dates are not reading as "unavailable" but I haven't figured out what's causing that yet and since it's already a known issue wanted to move forward with these changes before moving on to trying to help out with that one.

>
<strong>{monthTitle}</strong>
</div>
<table role="presentation">
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added the role of presentation here to stop the screen reader from reading all of the details of the columns and rows of the table WAI-ARIA use of presentation role.

className="CalendarMonth__caption js-CalendarMonth__caption"
>
<strong>{monthTitle}</strong>
</div>
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Changed <caption> to <div> as apparently this is a structural type element that cues screen readers that this is a data vs layout type table (see NOTE on "layout tables" and the caption element here: H39: Using caption elements to associate data table captions with data tables).

See in DayPicker.jsx that it now points here for the aria-describedby and thus continues to read the title as it was before with the <caption>.

@@ -825,6 +825,9 @@ export default class DayPicker extends React.Component {
<div
className={dayPickerClassNames}
style={dayPickerStyle}
role="application"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a role of application here as it seemed to match up with the WAI-ARIA example shown here: For application landmarks with static prose.


// initialize phrases
// set the appropriate CalendarDay phrase based on focusedInput
const chooseAvailableDate = getChooseAvailableDatePhrase(phrases, focusedInput);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It seemed like the chooseAvailableDate phrase was only getting set on update of the focus or the phrases so I thought adding the setting of it in componentDidMount would address initialization.

@@ -21,41 +21,45 @@ describe('SingleDatePickerInput', () => {
describe('clear date', () => {
describe('props.showClearDate is falsey', () => {
it('does not have .SingleDatePickerInput__clear-date class', () => {
const wrapper = shallow(<SingleDatePickerInput showClearDate={false} />);
const wrapper = shallow(<SingleDatePickerInput id="date" showClearDate={false} />);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

All the changes here were just to clean up some prop type warnings that were bugging me. Hope that's ok!

@coveralls
Copy link

Coverage Status

Coverage increased (+0.06%) to 86.558% when pulling d26aade on erin-doyle:a11y-improvements into c464d43 on airbnb:master.

@majapw
Copy link
Collaborator

majapw commented Sep 15, 2017

This is awesome! I will take a look at the code today. :) Thanks!

FYI @backwardok

@@ -825,6 +825,9 @@ export default class DayPicker extends React.Component {
<div
className={dayPickerClassNames}
style={dayPickerStyle}
role="application"
aria-label="Calendar"
Copy link
Contributor

Choose a reason for hiding this comment

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

We should make sure that this is something that can be localized

@@ -825,6 +825,9 @@ export default class DayPicker extends React.Component {
<div
className={dayPickerClassNames}
style={dayPickerStyle}
role="application"
aria-label="Calendar"
aria-describedby="CalendarMonth__caption"
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious if this could be confusing - since it will be most likely just read when the user comes to the calendar the first time, the description will always be the first month. They will come to an area that will read something like, "Calendar, application, September". Seems like a more appropriate description would be something like "Date picker"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm well, going back and testing this again to be sure, it seems as if it's not even reading the aria-describedby. I must have assumed that when I heard "September 2017" it was the aria-describedby but really it was just the reading of the selected CalendarDay. Upon changing the text it's now more obvious that it's not being read. Perhaps it's just not supported by the browser screen reader combinations I'm testing on (Mac with VoiceOver on Chrome, Firefox, Safari)? I so far can't find a good reason for why it's not being read...

I wonder if it's really necessary or if the aria-label here and the aria-describedby on the input telling the user how to interact with the calendar is enough information? If you think a describedby is crucial here let me know and I'll keep working on why it's not being read, otherwise I can just remove it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@majapw and @backwardok, I just wanted to bump this and ask again, what are your thoughts about whether to skip adding an aria-describedby on the role="application" div or to figure out why it's not being read and fix it. If the answer is to add it and fix it, what should the text be?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for the delay! I think it would be fine to bypass adding the aria-describedby here. If we were to have some sort of visual text that also explained the calendar, or there was text that explained some calendar instructions specifically for screen reader users, then that might be useful, but I think it's probably ok without it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No worries! I have added the aria-label text to the localizable phrases and removed the aria-describedby.

As for having something visual and read out loud explaining the calendar instructions, there already is the keyboard shortcuts modal which is opened by entering ? or clicking on the ? at the bottom of the calendar. The instructions for getting to the keyboard shortcuts modal is read via the aria-describedby on the DateInput:

Press the down arrow key to interact with the calendar and select a date. Press the question mark key to get the keyboard shortcuts for changing dates.

So the instructions seem pretty clear and complete from my testing as a visually able, keyboard only, and screen-reader user.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 86.425% when pulling e63f0cd on erin-doyle:a11y-improvements into c464d43 on airbnb:master.

@ljharb
Copy link
Member

ljharb commented Oct 3, 2017

@erin-doyle would you mind rebasing on the command line and force pushing, so as to remove any merge commits?

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 86.425% when pulling 9856f30 on erin-doyle:a11y-improvements into 0f6cccc on airbnb:master.

Erin Doyle added 6 commits October 3, 2017 13:27
… to the table thus differentiating the table as being for layout purposes instead of data for the sake of screen readers.
…r with appropriate aria attributes to better assist screen readers.
…rase based on the focusedInput which previously was not being set unless the focusedInput or phrases had changed.
@erin-doyle
Copy link
Collaborator Author

oh jeez I forget to rebase every time... Ok it should be cleaned up now.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 86.425% when pulling 479d6be on erin-doyle:a11y-improvements into 0f6cccc on airbnb:master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 86.425% when pulling 479d6be on erin-doyle:a11y-improvements into 0f6cccc on airbnb:master.

@@ -174,6 +183,22 @@ export default class DayPickerRangeController extends React.Component {
this.setDayPickerRef = this.setDayPickerRef.bind(this);
}

componentDidMount() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the determination of which phrase to show is based off of the props and doesn't rely on the actual state of the component (waiting for the input to have focus), you can do this logic in the constructor and set phrases to the new object in this.state.

If you put that logic in componentDidMount, which gets called after render, setting state will trigger another render.

Copy link
Member

Choose a reason for hiding this comment

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

(it'd then also need the same logic in componentWillReceiveProps)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep that totally makes sense, I've moved that logic to the constructor and removed componentDidMount. As for componentWillReceiveProps that logic was already there. The bit I added was to make sure that phrase was being initialized at component load time instead of having to wait until props changed.

…nentDidMount() and into the constructor to prevent extraneous rendering.
@coveralls
Copy link

Coverage Status

Coverage increased (+0.04%) to 86.396% when pulling 92df2a6 on erin-doyle:a11y-improvements into 0f6cccc on airbnb:master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.04%) to 86.396% when pulling 0f00b97 on erin-doyle:a11y-improvements into 0f6cccc on airbnb:master.

Copy link
Collaborator

@majapw majapw left a comment

Choose a reason for hiding this comment

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

Looks great to me!

@@ -132,6 +132,15 @@ const defaultProps = {
isRTL: false,
};

const getChooseAvailableDatePhrase = (phrases, focusedInput) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

slightly prefer:

function getChooseAvailableDatePhrase(phrases, focusedInput) {
  ...
}

over an arrow function but not a blocker

@majapw majapw merged commit 9f48d0e into react-dates:master Oct 5, 2017
@erin-doyle erin-doyle deleted the a11y-improvements branch October 6, 2017 00:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants