Skip to content

Commit

Permalink
feat: support both :focus and :focus-visible
Browse files Browse the repository at this point in the history
  • Loading branch information
Robbert committed Oct 18, 2021
1 parent b06e00a commit 62bfcbc
Show file tree
Hide file tree
Showing 39 changed files with 457 additions and 34 deletions.
5 changes: 5 additions & 0 deletions components/breadcrumb/bem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ ol.utrecht-breadcrumb__list {
z-index: 1;
}

.utrecht-breadcrumb__link--focus,
.utrecht-breadcrumb__link:focus {
background-color: var(--utrecht-breadcrumb-link-focus-background-color);
}

.utrecht-breadcrumb__link--focus::after,
.utrecht-breadcrumb__link:focus::after {
border-inline-start-color: var(--utrecht-breadcrumb-link-focus-background-color);
Expand Down
3 changes: 3 additions & 0 deletions components/button/bem.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const defaultArgs = {
busy: false,
disabled: false,
focus: false,
focusVisible: false,
hover: false,
textContent: '',
type: 'button',
Expand All @@ -21,6 +22,7 @@ export const Button = ({
busy = false,
disabled = false,
focus = false,
focusVisible = false,
hover = false,
textContent = '',
type = 'button',
Expand All @@ -30,5 +32,6 @@ export const Button = ({
'utrecht-button--busy': busy,
'utrecht-button--hover': hover,
'utrecht-button--focus': focus,
'utrecht-button--focus-visible': focusVisible,
'utrecht-button--disabled': disabled,
})}"${disabled ? ' aria-disabled="true"' : ''} type="${type}">${textContent}</button>`;
7 changes: 6 additions & 1 deletion components/button/bem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Copyright (c) 2021 The Knights Who Say NIH! B.V.
* Copyright (c) 2021 Gemeente Utrecht
*/
@import "../focus/bem";
@import "../common/focus/bem";

.utrecht-button {
background-color: var(--utrecht-button-primary-action-background-color, var(--utrecht-button-background-color));
Expand Down Expand Up @@ -55,6 +55,11 @@
color: var(--utrecht-button-active-color, var(--utrecht-button-color));
}

.utrecht-button--focus-visible,
.utrecht-button:focus-visible {
@include utrecht-focus-visible;
}

.utrecht-button--focus,
.utrecht-button:not(.utrecht-button--disabled):focus {
@include utrecht-focus;
Expand Down
21 changes: 21 additions & 0 deletions components/button/bem.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import "./bem.scss";
description: "Focus",
control: "boolean",
},
focusVisible: {
description: "Focus visible",
control: "boolean",
},
hover: {
description: "Hover",
control: "boolean",
Expand Down Expand Up @@ -110,6 +114,23 @@ Styling via the `.utrecht-button--focus` class name:
</Story>
</Canvas>

### Focus visible

Styling via the `.utrecht-button--focus-visible` class name:

<Canvas>
<Story
name="Button focus visible"
args={{
textContent: "Read more...",
focus: true,
focusVisible: true,
}}
>
{Button.bind({})}
</Story>
</Canvas>

### Disabled

Styling via the `.utrecht-button--disabled` class name:
Expand Down
11 changes: 11 additions & 0 deletions components/checkbox/bem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Copyright (c) 2021 Robbert Broersma
*/

@import "../common/focus/bem";

@mixin reset-input-checkbox {
margin-block-end: 0; /* reset native margin for input[type="checkbox"] */
margin-block-start: 0;
Expand All @@ -17,3 +19,12 @@
.utrecht-checkbox--disabled {
cursor: var(--utrecht-action-disabled-cursor);
}

.utrecht-checkbox--focus,
.utrecht-checkbox:focus {
@include utrecht-focus;
}

.utrecht-checkbox--focus-visible {
@include utrecht-focus-visible;
}
8 changes: 8 additions & 0 deletions components/checkbox/html.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@

.utrecht-html input[type="checkbox" i] {
@extend .utrecht-checkbox;

&:focus {
@extend .utrecht-checkbox--focus;
}

&:focus-visible {
@extend .utrecht-checkbox--focus-visible;
}
}
50 changes: 50 additions & 0 deletions components/common/focus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!--
@license EUPL-1.2
Copyright (c) 2021 Robbert Broersma
-->

# Focus

In CSS there are two important pseudo-classes we use: `:focus` and `:focus-visible`.

Browser support for [`:focus-visible`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible) unfortunately [does not include Safari 15](https://caniuse.com/css-focus-visible).

## Comparison

`:focus`:

- not visible on disabled interactive elements
- only in this state for a very short time when a mouse click or touch interaction triggers activation, for components such as Button or Link
- in this state for longer for elements that cannot be activated, such as a textbox
- remains in this state after activiation for some components, such as checkbox
- visual effect can vary a bit per component:
- different background color
- different border width
- different border color
- different text underline style

`:focus-visible`:

- visible even on disabled interactive elements
- looks very similar accross components, because user needs to keep track of focused element
- should draw attention

## Design for `:focus`

- The design for `:focus` should be distinguishable from the `:hover` design. When a user did not configure their system to always make focus visible using for example a focus ring, the user should be able to know which component will be activated when pressing `Enter`. When focus and hover state look the same, it might not possible to know which element would be activated.
- When using color, use a consistent color to convey focus. Choose a color that can be combined with other states, such as combining the red color for invalid state with your color for focus state.
- When changing the `font-weight` of a component as focus state, it might cause unintended layout shifts, so it might not be the ideal option.

## Design for `:focus-visible`

- A focus ring should not obscure the content inside the focused area.
- A focus ring should not obscure surrounding content. This consideration can also affect the minimum space between focusable elements.
- A focus ring should be visible with sufficient contrast on both dark and light backgrounds.
- A focus ring should be visible on backgrounds with unknown and mixed colors, such as background images.
- When using CSS with `overflow: hidden` in your component, the `outline` rendering can become partially or completely invisible. Be careful not to break the focus indication when using `overflow: hidden`.

## Related reading

- [Giving users and developers more control over focus - Chromium Blog](https://blog.chromium.org/2020/09/giving-users-and-developers-more.html)
- [The Focus-Indicated Pseudo-class: `:focus-visible` - W3C Selectors specification](https://www.w3.org/TR/selectors-4/#the-focus-visible-pseudo)
- [Understanding WCAG Success Criterion 2.4.7: Focus visible](https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-visible.html)
30 changes: 30 additions & 0 deletions components/common/focus/_bem.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @license EUPL-1.2
* Copyright (c) 2021 Gemeente Utrecht
* Copyright (c) 2021 Robbert Broersma
*/

@mixin utrecht-focus-ring {
box-shadow: 0 0 0 var(--utrecht-focus-box-shadow-spread-radius, 0) var(--utrecht-focus-box-shadow-color, transparent);
outline-color: var(--utrecht-focus-outline-color, transparent);
outline-offset: 0;
outline-style: var(--utrecht-focus-outline-style, solid);
outline-width: var(--utrecht-focus-outline-width, 0);
}

/* stylelint-disable-next-line block-no-empty */
@mixin utrecht-focus {
}

@mixin utrecht-focus-visible {
@include utrecht-focus-ring();
}

@mixin utrecht-focus-pseudo-classes {
:focus {
@include utrecht-focus;
}
:focus-visible {
@include utrecht-focus-visible;
}
}
File renamed without changes.
151 changes: 151 additions & 0 deletions components/common/focus/readme.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Canvas, Description, Meta, Story } from "@storybook/addon-docs";
import clsx from "clsx";
import { ComponentTokensTable } from "../../../documentation/components/ComponentTokensTable";
import tokens from "../../../proprietary/design-tokens/dist/index.json";
import readme from "./README.md";

export const bem = (block, modifiers, classNames) =>
clsx(
block,
classNames,
modifiers &&
Object.fromEntries(
Object.entries(modifiers)
.filter(([key]) => key)
.map(([key, value]) => [`${block}--${key}`, value])
)
);

export const FocusHTMLExample = () => `
<div class="utrecht-html">
<div><a href="https://example.com" target="_new">example.com</a></div>
<div><input type="text"></div>
<div><input type="checkbox"></div>
<div><input type="radio"></div>
<div><select><option>Example</option></select></div>
<div><textarea></textarea></div>
<div><button type="button">Click me</button></div>
</div>
`;

export const FocusBEMExample = ({ modifiers = {} }) => `
<div>
<div><a href="https://example.com" target="_new" class="${bem("utrecht-link", modifiers)}">example.com</a></div>
<div><input type="text" class="${bem("utrecht-textbox", modifiers, "utrecht-textbox--html-input")}"></div>
<div><input type="checkbox" class="${bem("utrecht-checkbox", modifiers, "utrecht-textbox--html-input")}"></div>
<div><input type="radio" class="${bem("utrecht-radio-button", modifiers, "utrecht-textbox--html-input")}"></div>
<div><select class="${bem(
"utrecht-select",
modifiers,
"utrecht-select--html-select"
)}"><option>Example</option></select></div>
<div><textarea class="${bem("utrecht-textarea", modifiers, "utrecht-textarea--html-textarea")}"></textarea></div>
<div><button type="button" class="${bem("utrecht-button", modifiers)}">Click me</button></div>
</div>
`;

<Meta title="Common Patterns/Focus" />

<Description>{readme}</Description>

## Example

### Focusable

<Canvas>
<Story
name="Focus with BEM"
parameters={{
docs: {
transformSource: (_src, { args }) => FocusBEMExample(args),
},
percy: { skip: true },
}}
>
{FocusBEMExample.bind({})}
</Story>
</Canvas>

### Focused

<Canvas>
<Story
name="Focus with BEM and :focus"
parameters={{
docs: {
transformSource: (_src, { args }) => FocusBEMExample(args),
},
percy: { skip: true },
}}
args={{
modifiers: {
focus: true,
},
}}
>
{FocusBEMExample.bind({})}
</Story>
</Canvas>

### Focus visible

<Canvas>
<Story
name="Focus with BEM and :focus-visible"
parameters={{
docs: {
transformSource: (_src, { args }) => FocusBEMExample(args),
},
percy: { skip: true },
}}
args={{
modifiers: {
"focus-visible": true,
},
}}
>
{FocusBEMExample.bind({})}
</Story>
</Canvas>

### Focus and focus visible

<Canvas>
<Story
name="Focus with BEM and both :focus and :focus-visible"
parameters={{
docs: {
transformSource: (_src, { args }) => FocusBEMExample(args),
},
percy: { skip: true },
}}
args={{
modifiers: {
focus: true,
"focus-visible": true,
},
}}
>
{FocusBEMExample.bind({})}
</Story>
</Canvas>

### Focusable HTML

<Canvas>
<Story
name="Focus with HTML"
parameters={{
docs: {
transformSource: (_src, { args }) => FocusHTMLExample(args),
},
percy: { skip: true },
}}
>
{FocusHTMLExample.bind({})}
</Story>
</Canvas>

## Design Tokens

<ComponentTokensTable tokens={tokens} component="utrecht-focus"></ComponentTokensTable>
6 changes: 4 additions & 2 deletions components/custom-checkbox/bem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

@import "../checkbox/bem";
@import "../focus/bem";
@import "../common/focus/bem";

.utrecht-custom-checkbox {
--utrecht-icon-size: var(--utrecht-custom-checkbox-icon-size, calc(0.75 * var(--utrecht-custom-checkbox-size)));
Expand Down Expand Up @@ -92,6 +92,8 @@
}

.utrecht-custom-checkbox__box--focus {
@include utrecht-focus();

background-color: var(
--utrecht-custom-checkbox-focus-background-color,
var(--utrecht-custom-checkbox-background-color)
Expand All @@ -102,7 +104,7 @@
}

.utrecht-custom-checkbox__box--focus-visible {
@include utrecht-focus();
@include utrecht-focus-visible();
}

.utrecht-custom-checkbox__input:focus ~ .utrecht-custom-checkbox__box {
Expand Down
7 changes: 0 additions & 7 deletions components/focus/_bem.scss

This file was deleted.

Loading

0 comments on commit 62bfcbc

Please sign in to comment.