-
Notifications
You must be signed in to change notification settings - Fork 55
feat(accessibility): add behaviours for tabs, tablists and toolbars #83
Conversation
}, | ||
anchor: { | ||
role: 'tab', | ||
'aria-selected': !!props['active'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you won't need double exclamation mark here, it can be just set to props['active']
in string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please test this with screen readers - double exclamation marks will turn anything into true/false. What is the behavior described by ARIA best practices? Should the screen reader always read "not selected" when you navigate to a tab that is not selected? or should it just read "selected" on the tab that is selected? if latter is the preferred way, I think it should be props['active'] ? true : undefined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was tested with both double exclamation and without. It works in both cases, reason why I added is because of ambiguity. there is also a double exclamation for SelectableListItemBehavior, 'aria-selected': !!props['active'],
. How should we go consistent?
Aria states we should have true and false as values. VoiceOver screen reader will not read 'not selected' if we have "false", it will just ignore it. ChromeVox, however, will read it as 'not selected'. Afaik we support VoiceOver only, but even with ChromeVox behaviour should be ok, it's just a different way to read it, and Aria accepts it too. https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html
root: { | ||
role: 'toolbar', | ||
'aria-label': props['aria-label'], | ||
'aria-disabled': !!props['disabled'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here regarding exclamation marks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similar to tab behavior. wouldn't the screen reader with this read 'not disabled' each time user navigates to a button? note that different readers might read it differently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will remove the double excl marks if we decide to be consistent with that approach.
attributes: { | ||
root: { | ||
role: 'tablist', | ||
'aria-label': props['aria-label'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not needed. It will come out of the box through the rest/unhandledProps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will remove it, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, this behavior (where rest
or unhandledProps
are passed down the components' tree) is not guaranteed by consumer of accessibility module. If we would like accesibility
module being an independent unit, these lines should be here
attributes: { | ||
root: { | ||
role: 'toolbar', | ||
'aria-label': props['aria-label'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not needed. It will come out of the box through the rest/unhandledProps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this one too
src/lib/accessibility/index.ts
Outdated
@@ -12,3 +12,6 @@ export { default as InputBehavior } from './Behaviors/Input/InputBehavior' | |||
export { default as TreeBehavior } from './Behaviors/Tree/TreeBehavior' | |||
export { default as TreeItemBehavior } from './Behaviors/Tree/TreeItemBehavior' | |||
export { default as GroupBehavior } from './Behaviors/Tree/GroupBehavior' | |||
export { default as TabBehavior } from './Behaviors/Tab/TabBehavior' | |||
export { default as TabListBehavior } from './Behaviors/Tab/TabListBehavior' | |||
export { default as ToolbarBehavior } from './Behaviors/Toolbar/ToolbarBehavior' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we also need a ToolbarButton behavior? Menu.Item renders a link with role='menuitem'
. Can you please test with screen readers as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aria does not specify any custom role for the menu items themselves when it comes to toolbar. only on the container itself.
However in the example https://www.w3.org/TR/wai-aria-practices/examples/toolbar/toolbar.html they use Buttons as menu items, and we use links by default. It is the reason I used component as child and it was fine, <li>
then <button>
.
But now, with latest master, I have an anchor tag that has a button as child. So it's <li> <a> <button>
which means double focus. And it's kind of incorrect as far as semantics are concerned.
I've tried a trick, and replaced Buttons with Icons, and it works. Icons now are acting as anchors. Should we leave it like this?
Another way is to override the anchor tag from the rendering MenuItem altogether and replace it with the button. Don't know if it's possible right now. I can check. What do you think?
313c4ea
to
d19b305
Compare
@@ -49,6 +49,16 @@ const Variations = () => ( | |||
description="A vertical menu may take the size of its container." | |||
examplePath="components/Menu/Variations/MenuExampleVerticalFluid" | |||
/> | |||
<ComponentExample | |||
title="Menu as a Tab List" | |||
description="A menu that acts as a tablist." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's change the description to "A menu with TabList accessibility behavior" and the one below to "A menu with toolbar accessibility behavior"
src/components/Menu/Menu.tsx
Outdated
@@ -46,6 +46,9 @@ class Menu extends AutoControlledComponent<any, any> { | |||
|
|||
/** Accessibility behavior if overridden by the user. */ | |||
accessibility: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), | |||
|
|||
/** Accessibility label that will be added by the accessibility behavior. */ | |||
'aria-label': PropTypes.string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we still need this? and line 71
Codecov Report
@@ Coverage Diff @@
## master #83 +/- ##
=======================================
Coverage 67.82% 67.82%
=======================================
Files 101 101
Lines 1386 1386
Branches 275 265 -10
=======================================
Hits 940 940
Misses 443 443
Partials 3 3
Continue to review full report at Codecov.
|
84cef80
to
e1d93bd
Compare
@silviuavram, may I, please, kindly ask you to update the description of this PR, so that it would be easier to onboard more reviewers :) Thanks! |
7729e06
to
efc818c
Compare
src/components/Menu/MenuItem.tsx
Outdated
@@ -31,6 +31,9 @@ class MenuItem extends UIComponent<any, any> { | |||
/** Shorthand for primary content. */ | |||
content: customPropTypes.contentShorthand, | |||
|
|||
/** A button can show it is currently unable to be interacted with. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
button -> menu item
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copy-paste done wrong. will fix it.
72d7e9e
to
61206f3
Compare
|
||
const disabledFlags = [true, false] | ||
disabledFlags.forEach(disabledValue => { | ||
test(`aria-disabled should be ${disabledValue} if button disabled prop is ${disabledValue}`, () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems that there is a error here, as we are setting disabled
for Menu
, not for Button
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to address problem of coupling rendering function with accessibility - lets rethink originally taken 'handledProps' approach, see related comment
61206f3
to
cf0c915
Compare
'aria-labelledby': props['aria-labelledby'], | ||
}, | ||
}, | ||
handledProps: ['aria-label', 'aria-labelledby'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aria-disabled
should be declared as handled
one as well, related change to anchor
's attribute value should be introduced:
anchor: {
...
'aria-disabled': props['disabled'] || props['aria-disabled'],
...
},
'aria-controls': props['aria-controls'], | ||
}, | ||
}, | ||
handledProps: ['aria-label', 'aria-labelledby', 'aria-controls'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aria-selected
should be introduced as handled
one, corresponding changes to attributes logic should be provided as well:
anchor: {
...
'aria-selected': props['active'] || props['aria-selected'],
...
},
…st-ui/react into siavram-tablist-behaviors
}, | ||
}, | ||
handledProps: ['aria-label', 'aria-labelledby', 'aria-expanded'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove aria-expanded if you are removing it from anchor part
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed this, wanted at first to add the same override as in tab and toolbar but I found that submenuOpened is probably a leftover as it's not being used. will remove this as well
{ key: 'editorials', content: 'Editorials', accessibility: TabBehavior }, | ||
{ key: 'review', content: 'Reviews', accessibility: TabBehavior }, | ||
{ key: 'events', content: 'Upcoming Events', accessibility: TabBehavior }, | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the accessibility prop always be the same across all MenuItems inside one Menu? If this is the case, we can just apply the accessibility prop to the Menu and provide the same as defaultProperty to all MenuItems inside the Menu. Take a look into the renderItem method in the Menu and decide if we should do this in the similar way as the other common props are delegated to the MenuItem components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, agree - we've discussed this moment, and agreed that it will be addressed by means of separate PR. For sure, there shouldn't be a mandatory requirement for consumer to specify this thing for menu items (especially given that those menu item objects could come as result of deserialising some HTTP response data, and at that moment there could be no context provided for them about which behavior Menu is using).
The way it could be achieved is by introducing behaviors
property to Accessibility Behavior:
const behavior = {
attributes: { .. },
behaviors: { item: TabBehavior }
}
We should create and address RFC (issue) for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That a good point! Just one thing to keep in mind, that we should leave the possibility to set different behaviors for Items (for e.g. case when one MenuItem has a submenu, others - don't).
So, in Menu, we can set default behaviors for items, unless a user has already set it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes - but this option is always opened to us, as it is still possible to explicitly specify behavior for each individual menu item via accessibility
prop:
<Menu items=[
...
{ key: '...', content: '...', accessibility: CustomBehavior }
]
Here we are touching just the fact that some reasonable default behavior should be applied to Menu
/List
items - without limiting the set of cases covered now.
it would be great if @levithomason would take a look on this PR as well |
This PR adds accessibility behaviours for Menus styled as a Tablist or Toolbar, along with examples of use.
As a side task, I've added the possibility to handle props in the component from the accessibility behaviour. I did it to be able to add attributes like 'aria-label' to other parts of the component other than the default. For instance, in the Menu case, I want to add the 'aria-label' passed as a prop to the component into the
<a>
element instead of the<li>
.