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

Implement keyboard and focus management #3897

Closed
wants to merge 3 commits into from

Conversation

barklund
Copy link
Contributor

@barklund barklund commented Dec 7, 2019

Summary

This implements proper semantic and accessible keyboard and focus management.

This is work in progress. Please see individual commit messages for details.

  • Tabs
  • Global shortcuts
  • Local shortcuts
  • Media list

Fixes #3826.

Checklist

  • My pull request is addressing an open issue (please create one otherwise).
  • My code is tested and passes existing tests.
  • My code follows the Engineering Guidelines (updates are often made to the guidelines, check it out periodically).

Currently it's only in use for the tabs in the Library. If you focus any tab in the library, pressing tab and shift-tab will move correctly back and forwards between the tabs and pressing enter will switch selection to the focused tab.

However, pressing left and right arrows will also navigate to the other tabs (even observing reading direction), but of course not outside the tab list.

This should be fully accessible and semantic..
@barklund barklund added Accessibility Changes that impact accessibility and need corresponding review (e.g. markup changes). Group: Stories Editing Stories Stories Editor labels Dec 7, 2019
@barklund barklund self-assigned this Dec 7, 2019
@googlebot googlebot added the cla: yes Signed the Google CLA label Dec 7, 2019
@barklund
Copy link
Contributor Author

barklund commented Dec 7, 2019

Current early implementation only works for tabs:

focusable

Note that you can use the mouse to navigate and focus, you can use tab and shift+tab to focus and then enter to select, but you can also use the arrow keys, left and right (observing reading direction).

Using tab allows you to of course tab to other sections of the application, whereas using arrow keys only let you navigate between the tabs and not any further.

Page carousel now also uses navigable and navigablegroup - and you can press delete to delete the currently focused page.

&:hover {
background-color: ${ ( { theme } ) => theme.colors.fg.v1 };
}

&:focus, &:active {
outline: none;

Choose a reason for hiding this comment

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

General believe, i think, is that outlines are good for accessibility. Is there any reason you don't like it here? I think replacing it with border is fine, but sometimes there are other factors. For instance, if UX asks to add an extra pixel to the outline, very little needs to change, vs making border thicker could cause relayout. But other than that, this is not critical.

Copy link
Contributor Author

@barklund barklund Dec 11, 2019

Choose a reason for hiding this comment

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

I'm am open to using either outline or border, no preference. However, the UX called for a specific design with a specific border and I wasn't able to replicate it using outline only. For a11y purposes I think we're safe as long as we do something else to provide highlight.

Choose a reason for hiding this comment

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

Can you point me to the precise place in figma with that style. I just want to confirm it once so we don't have to stumble on this each time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's shown in the section about deleting a page from the carousel:

Screen Shot 2019-12-11 at 2 51 48 PM

Choose a reason for hiding this comment

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

Ok. We'd just need to always reserve a border and only manipulate the border-color from transparent to focused color.

const setRef = useCombinedRefs( ref, forwardedRef );

useLayoutEffect( () => {
const mt = MouseTrap( ref.current );

Choose a reason for hiding this comment

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

Just to clarify: this shouldn't be new MouseTrap, should it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mousetrap doesn't use new in their docs, but I do see that WP does in their implementation, that I was inspired by.

I feel that I remember seeing something about new syntax being discouraged, but I can't find it again.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The docs say:

You can also create a new Mousetrap instance if you want to bind a bunch of things to the same element.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, I don't really know what the difference is here (that is, the actual effect of doing either). The current implementation works, but maybe not efficiently?


function Focusable( { shortcuts, hasFocus, isGlobal, element, className, role, tabindex, forwardedRef, children, ...rest } ) {
const ref = useRef();
const setRef = useCombinedRefs( ref, forwardedRef );

Choose a reason for hiding this comment

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

Why not just use React.forwardRef?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should also use forwardRef, yes (which allows me to use ref={} in the parent component), but I still need some way to merge the two refs, when needing the ref both in the component itself and in a parent component. It can't be done any other way, unfortunately.

Choose a reason for hiding this comment

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

A single forwarded ref usually does the trick for me, but sure, if there's need, let's keep.

const focus = ( node ) => node && node.focus();

if ( direction === NavigableGroup.DIRECTION_HORIZONTAL ) {
const { forward, backward } = getDirectionalKeysForNode( ref.current );

Choose a reason for hiding this comment

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

This makes sense, but to clarify: what does Tab do inside the NavigableGroup?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pressing tab navigates between focusable elements using browser-native implementation. Just like enter clicks the currently focused element - this is also using browser-native behaviour. I believe that's excellent for a11y.

Choose a reason for hiding this comment

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

Ok. I think that's good. In some systems tab jumps between bigger blocks and arrows are used to navigate within the block. But I think for now a simple way is better. This might pop up for e.g. gallery, where there might be unlimited set of things to tab over, which could make tab harder to use to navigate to other elements.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looking at the tablist implementation at https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html and the one I built for WordPress core (click on share icon on https://wordpress.org/news/2019/12/wordpress-5-3-1-security-and-maintenance-release/embed/), pressing tab jumps to the current tab's content, not to the next tab.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking at the tablist implementation at https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html and the one I built for WordPress core (click on share icon on https://wordpress.org/news/2019/12/wordpress-5-3-1-security-and-maintenance-release/embed/), pressing tab jumps to the current tab's content, not to the next tab.

Thanks a lot for those links - very helpful!

Those definitely are different ways of implementing this. The inactive tabs don't participate in tab order at all it seems (tabindex="-1") - only the active tab and the tab content. tab and shift+tab then navigate between the active tab and the tab content in the regular way based off document order.

And they have an example of both automatic and manual tab switching. Very interesting approaches - I will read up on those!

Copy link

@dvoytenko dvoytenko left a comment

Choose a reason for hiding this comment

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

Few comments, but otherwise LG

const Tabs = styled( NavigableGroup ).attrs( {
element: 'nav',
role: 'tablist',
tabindex: -1,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the default focus CSS (outline) also be removed here? (Since it can still focus by clicking)

This was referenced Jan 7, 2020
@spacedmonkey
Copy link
Contributor

This PR is over a month out of date. So can merge for develop-stories.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Accessibility Changes that impact accessibility and need corresponding review (e.g. markup changes). cla: yes Signed the Google CLA Stories Stories Editor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants