Skip to content

Commit

Permalink
Make checkboxes more accessible
Browse files Browse the repository at this point in the history
Make the underlying native checkboxes more accessible to screen readers and touch.

Resolves #276. Addresses parts of #163.

Co-Authored-By: Dickson Tan <[email protected]>
  • Loading branch information
jakezatecky and Neurrone committed Mar 15, 2023
1 parent fc8d660 commit 829fc28
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#### Accessibility

* Make the underlying checkbox accessible to screen readers and touch (#276)
* Hide the pseudo-checkbox from the accessibility tree
* Change the clickable label from `role="link"` to `role="button"`

Expand Down
20 changes: 5 additions & 15 deletions src/js/components/TreeNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ class TreeNode extends React.PureComponent {
super(props);

this.onCheck = this.onCheck.bind(this);
this.onCheckboxKeyPress = this.onCheckboxKeyPress.bind(this);
this.onCheckboxKeyUp = this.onCheckboxKeyUp.bind(this);
this.onClick = this.onClick.bind(this);
this.onExpand = this.onExpand.bind(this);
Expand All @@ -68,19 +67,14 @@ class TreeNode extends React.PureComponent {
});
}

onCheckboxKeyPress(event) {
onCheckboxKeyUp(event) {
const { checkKeys } = this.props;
const { key } = event;

// Prevent browser scroll when pressing space on the checkbox
if (key === KEYS.SPACEBAR && checkKeys.includes(key)) {
// Prevent default spacebar behavior from interfering with user settings
if (KEYS.SPACEBAR) {
event.preventDefault();
}
}

onCheckboxKeyUp(event) {
const { checkKeys } = this.props;
const { key } = event;

if (checkKeys.includes(key)) {
this.onCheck();
Expand Down Expand Up @@ -224,16 +218,12 @@ class TreeNode extends React.PureComponent {
indeterminate={checked === 2}
onChange={() => {}}
onClick={this.onCheck}
onKeyUp={this.onCheckboxKeyUp}
/>
<span
aria-checked={checked === 1}
aria-disabled={disabled}
aria-hidden="true"
className="rct-checkbox"
role="checkbox"
tabIndex={0}
onKeyPress={this.onCheckboxKeyPress}
onKeyUp={this.onCheckboxKeyUp}
role="presentation"
>
{this.renderCheckboxIcon()}
</span>
Expand Down
51 changes: 47 additions & 4 deletions src/scss/react-checkbox-tree.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ $rct-label-hover: rgba($rct-icon-color, .1) !default;
$rct-label-active: rgba($rct-icon-color, .15) !default;
$rct-clickable-hover: rgba($rct-icon-color, .1) !default;
$rct-clickable-focus: rgba($rct-icon-color, .2) !default;
$rct-checkbox-outline-offset: 2px !default;
$rct-outline-color: rgba($rct-icon-color, .5) !default;
$rct-outline-radius: 2px;
$rct-outline-size: 2px !default;
$rct-outline-offset: -2px !default;

// Force ASCII output until Sass supports it
// https://github.com/sass/dart-sass/issues/568
Expand All @@ -30,20 +35,38 @@ $rct-clickable-focus: rgba($rct-icon-color, .2) !default;
}
}

button,
input {
&:focus {
outline: $rct-outline-size solid $rct-outline-color;
border-radius: $rct-outline-radius;
}

&:not(:focus-visible) {
outline: none;
}
}

button {
line-height: normal;
color: inherit;

&:disabled {
cursor: not-allowed;
}

&:focus {
outline-offset: $rct-outline-offset;
}
}

.rct-bare-label {
cursor: default;
}

label {
display: flex;
align-items: center;
margin-bottom: 0;
cursor: pointer;

Expand All @@ -57,12 +80,32 @@ $rct-clickable-focus: rgba($rct-icon-color, .2) !default;
}
}

&:not(.rct-native-display) input {
display: none;
input {
margin: 0 5px;
cursor: pointer;

&:focus {
outline-offset: $rct-checkbox-outline-offset;
}
}

&.rct-native-display input {
margin: 0 5px;
&:not(.rct-native-display) input {
// Hide the native checkbox (but keep in accessibility tree and accessible via touch)
position: absolute;
opacity: 0;

// Show focus effect on pseudo-checkbox
&:focus {
+ .rct-checkbox {
outline: $rct-outline-size solid $rct-outline-color;
outline-offset: $rct-outline-offset;
border-radius: $rct-outline-radius;
}

&:not(:focus-visible) + .rct-checkbox {
outline: none;
}
}
}

.rct-icon {
Expand Down
4 changes: 2 additions & 2 deletions test/CheckboxTree.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ describe('<CheckboxTree />', () => {
it('should trigger a check event when pressing one of the supplied values', async () => {
let actual = null;

const { container } = render(
render(
<CheckboxTree
checkKeys={['Shift']}
checked={[]}
Expand All @@ -171,7 +171,7 @@ describe('<CheckboxTree />', () => {
/>,
);

await fireEvent.keyUp(container.querySelector('.rct-checkbox'), { key: 'Shift' });
await fireEvent.keyUp(screen.getByRole('checkbox'), { key: 'Shift' });

assert.deepEqual(actual, ['jupiter']);
});
Expand Down
4 changes: 2 additions & 2 deletions test/TreeNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ describe('<TreeNode />', () => {
it('should trigger on key press', async () => {
let actual = {};

const { container } = render(
render(
<TreeNode
{...baseProps}
checked={2}
Expand All @@ -389,7 +389,7 @@ describe('<TreeNode />', () => {
/>,
);

await fireEvent.keyUp(container.querySelector('.rct-checkbox'), { key: 'Enter' });
await fireEvent.keyUp(screen.getByRole('checkbox'), { key: 'Enter' });

assert.isTrue(actual.checked);
});
Expand Down

0 comments on commit 829fc28

Please sign in to comment.