Skip to content

Room UI Redesign Accessibility: Focus Layers #3477

@robertlong

Description

@robertlong

Background

In the redesign, we're putting in the work to ensure that Hubs has excellent keyboard navigation support for accessibility. Currently, all of the new elements have proper focused element styling and tab index ordering is getting close to correct. However, one area we are severely lacking is trapping focus when a modal, popover, or other full screen element is visible.

From the WAI-ARIA Authoring Practices Document:

A dialog is a window overlaid on either the primary window or another dialog window. Windows under a modal dialog are inert. That is, users cannot interact with content outside an active dialog window. Inert content outside an active dialog is typically visually obscured or dimmed so it is difficult to discern, and in some implementations, attempts to interact with the inert content cause the dialog to close.

Like non-modal dialogs, modal dialogs contain their tab sequence. That is, Tab and Shift + Tab do not move focus outside the dialog. However, unlike most non-modal dialogs, modal dialogs do not provide means for moving keyboard focus outside the dialog window without closing the dialog.

I've began to implement focus-layers in Hubs, but I've ran into a few design issues and I'd like to get some feedback on my implementation before continuing.

Focus Layers

The team at Discord have developed a library called focus-layers that helps implement these sorts of focus interactions in React. As a developer, I can add focus layers to components like Modals and Popovers and the focus will be isolated to those components. When the component unmounts (is removed from the DOM) that layer will be popped off the focus layer stack, my focus will return to the previously focused element in the parent layer, and I'll be able to focus items in the parent layer again. It's a great library that manages to simplify a really hard problem.

The redesign has quite a few components that trap focus. Here's a diagram of them:
Focus layer diagram, containing canvas, ui root, fullscreen layout, modal, sidebar, and popover components

Canvas

We can probably think of the canvas / 3D viewport as the bottom most focus layer or perhaps it's not really a focus layer at all. If it is thought of as a focus layer and if you have nothing focused in the UI, your keyboard input should go to it. If it's not a focus layer, then we can send all of the global keyboard shortcuts to the canvas so long as an input element like a text input isn't focused. This is somewhat complicated by the fact that we have bound the Tab, Space, Escape, and Arrow keys to actions in the 3D viewport.

Two solutions for this that I've come up with are:

  1. The canvas element takes focus and 3D viewport hotkeys are only received when it is focused. This means we can avoid remapping most of these hotkeys. However we probably want to remove the keyboard mapping for tab so that users can change focus to elements in the UI root. Pressing the escape key in the UI root, tabbing to it, or clicking on the canvas would focus the canvas.

  2. The canvas element takes focus, but the 3D viewport hotkeys are bound globally. The Tab and Spacebar keys would be unbound. Escape would be used in both the 3D and 2D context depending on what is in focus to. For example, it is used in to cancel drawing with the pen. The arrow keys would be bound to movement when the canvas is focused, or whenever we can detect that you are a "mouse user". Currently you start out as a mouse user and a keyboard user is detected when the user presses the tab key. Clicking anywhere on the page switches you back to a mouse user. With this method, you can use the arrow keys to move around in 3D space and still use them to scroll through lists, etc. The WASD keys are globally bound and can always be used to move around in 3D space.

UI Root

The UI Root component is, as it's name suggests, the root of the application. When you load a Hubs room and press tab, the first element that is focused, is the join room button.

Screenshot of the room entry panel with the join room button focused

In the above screenshot, the join room button is focused, and you can move focus to the bottom toolbar. For example the next focus item after the "Options" button is the "Invite" button in the bottom toolbar and the item after the "More" button in the bottom right, is the "People" button in the top right.

Once you are in the room, there are more items in that bottom toolbar that can be toggled or open popovers/sidebars.

In-room screenshot with additional toolbar items

Popover

The popover component traps focus when opened. Focus currently starts on the close button, but when properly implemented will focus the next element. Pressing tab repeatedly will eventually reach the end of the focusable elements in the popover and loop back around to the first focusable element. Pressing the escape key or unmounting the popover (triggered via the close button or another action) will untrap the focus.

Screenshot of Hubs with the Popover open and the close button focused

Modal

Modals work the same way as popovers, except sometimes they can't be closed via the escape key.

Screenshot of the close room modal

Fullscreen Layout

The focus layer for the fullscreen layout component also works the same way as modals and popovers, but it always takes up the whole screen and obscures the rest of the content. The media browser, avatar editor, and preferences page are examples of this component.

Screenshot of the media browser

Sidebar

The sidebar component only traps focus when it is full screen. On mobile devices or smaller browser windows, the sidebar is full screen and obscures the other content. Focusing elements outside the sidebar when it is fullscreen wouldn't make sense because you can't see these items, so we only enable the focus layer when it is fullscreen. When the focus is inside the sidebar, you can press escape to hide it or navigate backwards.

Sidebar on a large screen

Sidebar on a mobile device where it takes up the fullscreen

Summary and Action Items

Overall, focus layers allow for easy and correct keyboard navigation, but I could use more opinions on where they are used, how they are implemented, and for someone to check my understanding. I would especially like input from someone who has extensively used or studied interfaces that implement these patterns correctly, because I'm still pretty new to this.

The biggest design decision is how to handle the 3D viewport keybindings in combination with the standard keyboard navigation controls. I haven't fully thought through either of the two options I've proposed, but I think they are good starting points.

I'm going to pause on the technical work until we've had time to discuss the design, if anyone would like to see a demo, you can check out that branch or we could stand it up on our staging server. I'm happy to walk people through a demo.

┆Issue is synchronized with this Jira Task

Metadata

Metadata

Assignees

Labels

Epicdpx-hubsMark issues that need to go into the XD/Buildout team for collaborationjira-hubsredesignIssues related to the room UI redesign

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions