Skip to content

Commit

Permalink
feat: add support for nested focus-visible #69
Browse files Browse the repository at this point in the history
  • Loading branch information
blackfalcon committed May 14, 2021
1 parent fd397ff commit 478c2bf
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 4 deletions.
70 changes: 70 additions & 0 deletions docs/focus-visible.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Accessibility focus visible

One of the core pillars for supporting accessibility (a11y) within a modern web application is through maintaining the state of focus as a user tabs through an interface. The current implementation of this a11y state is via the `:focus` state introduced in CSS Level 2 (Revision 1).

For a11y reasons, according to the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus#Accessibility_Concerns), it is advised to NEVER remove the browsers default settings without replacing it with a sufficient substitute.

```css
:focus { outline: none; }
```

> Never just remove the focus outline (visible focus indicator) without replacing it with a focus outline that will pass [WCAG 2.1 SC 1.4.11 Non-Text Contrast](https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html)
## Design concerns

In contrast to the a11y issue, there is typically a design concern with the `:focus` state as an element within a web app has focus regardless of tab, click or tap. As a result of that, unless there is a requirement to maintain a11y the `:focus` state is typically disabled.

For projects that have an a11y requirement, this is not an option. This in itself is a large design challenge and one with many opportunities.

## Selectors Level 4 specification

In the next selector level specification draft is [9.4. The Focus-Indicated Pseudo-class: :focus-visible](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo).

> The `:focus-visible` pseudo-class applies while an element matches the `:focus` pseudo-class and the UA (User Agent) determines via heuristics that the focus should be made evident on the element. (Many browsers show a “focus ring” by default in this case.)
-- [:focus-visible](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible)

The purpose of this new CSS spec is to allow for developers to build interfaces that speaks specifically to their special needs audience while not having to adjust the experience for those who do not require additional interaction feedback. This also allows for designers to design very specific interactions for those who require additional interaction feedback.

## Focus visible browser support

With [increased browser support](https://caniuse.com/?search=focus-visible) the `:focus-visible` rarely requires polyfill support. Use of this selector is consistent with any CSS pseudo-class.

```css
:focus-visible {
background-color: var(--auro-color-border-error-on-light);
color: var(--auro-color-base-white);
}
```

Something to remember, browser defaults have a similar outline style with `:focus-visible` as with a standard `:focus` selector.

## Focus visible polyfill

The Auro WC-Generator supports the focus-visible [polyfill](https://www.npmjs.com/package/focus-visible), based on the proposed CSS pseudo selector, and allows for this functionality for remaining non-supporting browsers.

> Based on the proposed CSS :focus-visible pseudo-selector, this prototype adds a focus-visible class to the focused element, in situations in which the :focus-visible pseudo-selector should match.
The polyfill has been tested in a number of situations and has great support from the community and its users.

## Focus visible, Auro custom elements, native and polyfill support

It is important to remember that the focus-visible polyfill does NOT reach into the shadow DOM of a custom element. When tabbing through an interface, the `.focus-visible` selector will be placed on the host tag of a custom element. The CSS within the custom element will need to be aware of the outer host's appearance of the `.focus-visible` selector. The following example is how to write a CSS selector that will be inside the shadow DOM, but aware of updates to the host custom element tag.

```css
:host(.focus-visible) {
.button {
background-color: red;
}
}
```

When the `:focus-visible` pseudo-class is available to the browser, the polyfill does not conflict with the `.focus-visible` selector in the DOM. In order to support both formats, it is necessary to duplicate the styles for this interaction. The pseudo-class will be applied to the actual element in the shadow DOM that was tabbed to, versus the outer tag of the custom element, so there is no need to use the `:host()` selector as a reference.

```css
:focus-visible {
background-color: red;
}
```

WCSS has support for the [.focus-visible](https://alaskaairlines.github.io/WebCoreStyleSheets/#accessibility-css) selector as well as the [:focus-visible](https://alaskaairlines.github.iodocs/#core-css-#{$scope}%20*) pseudo-class selector for a baseline experience.
12 changes: 8 additions & 4 deletions template/src/[namespace]-[name].js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { LitElement, html } from "lit-element";
import "focus-visible/dist/focus-visible.min.js";
import styleCss from "./style-css.js";
import styleCssFixed from './style-fixed-css.js';
import { isFocusVisibleSupported, isFocusVisiblePolyfillAvailable } from './util';

// See https://git.io/JJ6SJ for "How to document your components using JSDoc"
/**
Expand All @@ -26,9 +27,12 @@ import styleCssFixed from './style-fixed-css.js';

// build the component class
class [Namespace][Name] extends LitElement {
// constructor() {
// super();
// }
constructor() {
super();
if (!isFocusVisibleSupported() && isFocusVisiblePolyfillAvailable()) {
window.applyFocusVisiblePolyfill(this.shadowRoot);
}
}

// function to define props used within the scope of this component
static get properties() {
Expand All @@ -51,7 +55,7 @@ class [Namespace][Name] extends LitElement {
// function that renders the HTML and CSS into the scope of the component
render() {
return html`
<div class=${this.cssClass}>
<div class=${this.cssClass} tabindex="0">
<slot></slot>
</div>
`;
Expand Down
15 changes: 15 additions & 0 deletions template/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,18 @@
border: 1px solid var(--auro-color-border-error-on-light);
color: var(--auro-color-border-error-on-light);
}

// Selector for native :focus-visible support
:focus-visible {
background-color: var(--auro-color-border-error-on-light);
color: var(--auro-color-base-white);
}

// Selector for focus-visible polyfill
:host(.focus-visible) {
// requires reference to custom element selector in this example
.testClass {
background-color: var(--auro-color-border-error-on-light);
color: var(--auro-color-base-white);
}
}
18 changes: 18 additions & 0 deletions template/src/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
// See LICENSE in the project root for license information.

// ---------------------------------------------------------------------

export function isFocusVisibleSupported() {
try {
document.querySelector(':focus-visible');
} catch {
return false;
}
return true;
}

// https://github.com/WICG/focus-visible#shadow-dom
export function isFocusVisiblePolyfillAvailable() {
return window.applyFocusVisiblePolyfill != null;
}

0 comments on commit 478c2bf

Please sign in to comment.