Skip to content

Commit

Permalink
Select component a11y fixes (#2038)
Browse files Browse the repository at this point in the history
* fixes disabled option colors for all browsers (Firefox had the most issues)

* fixes custom arrow color for disabled state and Windows high contrast mode, and hacks around Firefox quirks

* updates SelectInput stories to use FormControl

* makes cursor behavior consistent for inputs

* removes redundant ARIA attributes from <select>

* updates tests and snapshots

* adds changeset

* fixes media query for forced colors (high contrast mode)

* hacks around Firefox Windows high-contrast mode quirk

* addresses a11y feedback

* fixes linting
  • Loading branch information
mperrotti authored May 4, 2022
1 parent 1bfaaa1 commit 1c2eeb9
Show file tree
Hide file tree
Showing 10 changed files with 631 additions and 101 deletions.
5 changes: 5 additions & 0 deletions .changeset/cold-pillows-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': patch
---

Fixes accessibility bugs in the Select component.
6 changes: 3 additions & 3 deletions docs/content/Select.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {Select, Text} from '@primer/react'
```jsx live
<FormControl>
<FormControl.Label>Preferred Primer component interface</FormControl.Label>
<Select id="basic">
<Select>
<Select.Option value="figma">Figma</Select.Option>
<Select.Option value="css">Primer CSS</Select.Option>
<Select.Option value="prc">Primer React components</Select.Option>
Expand All @@ -30,7 +30,7 @@ import {Select, Text} from '@primer/react'
```jsx live
<FormControl>
<FormControl.Label>Preferred Primer component interface</FormControl.Label>
<Select id="grouped">
<Select>
<Select.OptGroup label="GUI">
<Select.Option value="figma">Figma</Select.Option>
</Select.OptGroup>
Expand All @@ -48,7 +48,7 @@ import {Select, Text} from '@primer/react'
```jsx live
<FormControl>
<FormControl.Label>Preferred Primer component interface</FormControl.Label>
<Select id="withPlaceholder" placeholder="Pick an interface">
<Select placeholder="Pick an interface">
<Select.Option value="figma">Figma</Select.Option>
<Select.Option value="css">Primer CSS</Select.Option>
<Select.Option value="prc">Primer React components</Select.Option>
Expand Down
43 changes: 32 additions & 11 deletions src/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react'
import styled from 'styled-components'
import {get} from './constants'
import TextInputWrapper, {StyledWrapperProps} from './_TextInputWrapper'

export type SelectProps = Omit<
Expand All @@ -10,19 +9,35 @@ export type SelectProps = Omit<

const StyledSelect = styled.select`
appearance: none;
background-color: transparent;
border: 0;
color: currentColor;
font-size: inherit;
outline: none;
width: 100%;
option {
color: initial;
/* Firefox hacks: */
/* 1. Makes Firefox's native dropdown menu's background match the theme.
background-color should be 'transparent', but Firefox uses the background-color on
<select> to determine the background color used for the dropdown menu.
*/
background-color: inherit;
/* 2. Prevents visible overlap of partially transparent background colors.
'colors.input.disabledBg' happens to be partially transparent in light mode, so we use a
transparent background-color on a disabled <select>. */
&:disabled {
background-color: transparent;
}
/* colors the select input's placeholder text */
&:invalid {
color: ${get('colors.fg.subtle')};
/* 3. Maintain dark bg color in Firefox on Windows high-contrast mode
Firefox makes the <select>'s background color white when setting 'background-color: transparent;' */
@media screen and (forced-colors: active) {
&:disabled {
background-color: -moz-combobox;
}
}
`

Expand All @@ -44,18 +59,24 @@ const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
({children, disabled, placeholder, size, required, validationStatus, ...rest}: SelectProps, ref) => (
<TextInputWrapper
sx={{
position: 'relative'
overflow: 'hidden',
position: 'relative',
'@media screen and (forced-colors: active)': {
svg: {
fill: disabled ? 'GrayText' : 'FieldText'
}
}
}}
size={size}
validationStatus={validationStatus}
disabled={disabled}
>
<StyledSelect
ref={ref}
required={required || Boolean(placeholder)}
required={required}
disabled={disabled}
aria-required={required}
aria-disabled={disabled}
aria-invalid={validationStatus === 'error' ? 'true' : 'false'}
data-hasplaceholder={Boolean(placeholder)}
{...rest}
>
{placeholder && (
Expand Down
17 changes: 15 additions & 2 deletions src/_TextInputWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,19 @@ export const TextInputBaseWrapper = styled.span<StyledBaseWrapperProps>`
border-radius: ${get('radii.2')};
outline: none;
box-shadow: ${get('shadows.primer.shadow.inset')};
cursor: text;
display: inline-flex;
align-items: stretch;
min-height: 32px;
input,
textarea {
cursor: text;
}
select {
cursor: pointer;
}
&::placeholder {
color: ${get('colors.fg.subtle')};
}
Expand Down Expand Up @@ -125,10 +133,15 @@ export const TextInputBaseWrapper = styled.span<StyledBaseWrapperProps>`
${props =>
props.disabled &&
css`
cursor: not-allowed;
color: ${get('colors.primer.fg.disabled')};
background-color: ${get('colors.input.disabledBg')};
border-color: ${get('colors.border.default')};
input,
textarea,
select {
cursor: not-allowed;
}
`}
${props =>
Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe('Select', () => {
const placeholderOption = getByText('Pick a choice')
const select = getByLabelText('Choice')

expect(select).not.toHaveAttribute('aria-required')
expect(select).not.toHaveAttribute('required')

expect(placeholderOption).toBeDefined()
expect(placeholderOption.tagName.toLowerCase()).toBe('option')
Expand Down Expand Up @@ -102,7 +102,7 @@ describe('Select', () => {
const placeholderOption = getByText('Pick a choice')
const select = getByLabelText('Choice')

expect(select).toHaveAttribute('aria-required')
expect(select).toHaveAttribute('required')

expect(placeholderOption).toBeDefined()
expect(placeholderOption.tagName.toLowerCase()).toBe('option')
Expand Down Expand Up @@ -130,7 +130,7 @@ describe('Select', () => {

const select = getByLabelText('Choice')

expect(select).toHaveAttribute('aria-disabled')
expect(select).toHaveAttribute('disabled')
expect(select).toHaveAttribute('disabled')
})
})
70 changes: 63 additions & 7 deletions src/__tests__/__snapshots__/Autocomplete.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Array [
border-radius: 6px;
outline: none;
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
cursor: text;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
Expand All @@ -28,6 +27,15 @@ Array [
padding-right: 0;
}
.c0 input,
.c0 textarea {
cursor: text;
}
.c0 select {
cursor: pointer;
}
.c0::-webkit-input-placeholder {
color: #6e7781;
}
Expand Down Expand Up @@ -163,7 +171,6 @@ Array [
border-radius: 6px;
outline: none;
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
cursor: text;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
Expand All @@ -179,6 +186,15 @@ Array [
padding-right: 0;
}
.c0 input,
.c0 textarea {
cursor: text;
}
.c0 select {
cursor: pointer;
}
.c0::-webkit-input-placeholder {
color: #6e7781;
}
Expand Down Expand Up @@ -349,7 +365,6 @@ Array [
border-radius: 6px;
outline: none;
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
cursor: text;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
Expand All @@ -365,6 +380,15 @@ Array [
padding-right: 0;
}
.c0 input,
.c0 textarea {
cursor: text;
}
.c0 select {
cursor: pointer;
}
.c0::-webkit-input-placeholder {
color: #6e7781;
}
Expand Down Expand Up @@ -1298,7 +1322,6 @@ Array [
border-radius: 6px;
outline: none;
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
cursor: text;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
Expand All @@ -1314,6 +1337,15 @@ Array [
padding-right: 0;
}
.c0 input,
.c0 textarea {
cursor: text;
}
.c0 select {
cursor: pointer;
}
.c0::-webkit-input-placeholder {
color: #6e7781;
}
Expand Down Expand Up @@ -2157,7 +2189,6 @@ Array [
border-radius: 6px;
outline: none;
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
cursor: text;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
Expand All @@ -2173,6 +2204,15 @@ Array [
padding-right: 0;
}
.c0 input,
.c0 textarea {
cursor: text;
}
.c0 select {
cursor: pointer;
}
.c0::-webkit-input-placeholder {
color: #6e7781;
}
Expand Down Expand Up @@ -3027,7 +3067,6 @@ Array [
border-radius: 6px;
outline: none;
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
cursor: text;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
Expand All @@ -3043,6 +3082,15 @@ Array [
padding-right: 0;
}
.c0 input,
.c0 textarea {
cursor: text;
}
.c0 select {
cursor: pointer;
}
.c0::-webkit-input-placeholder {
color: #6e7781;
}
Expand Down Expand Up @@ -4027,7 +4075,6 @@ Array [
border-radius: 6px;
outline: none;
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
cursor: text;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
Expand All @@ -4043,6 +4090,15 @@ Array [
padding-right: 0;
}
.c0 input,
.c0 textarea {
cursor: text;
}
.c0 select {
cursor: pointer;
}
.c0::-webkit-input-placeholder {
color: #6e7781;
}
Expand Down
Loading

0 comments on commit 1c2eeb9

Please sign in to comment.