Skip to content

Commit 1c2eeb9

Browse files
authored
Select component a11y fixes (#2038)
* 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
1 parent 1bfaaa1 commit 1c2eeb9

File tree

10 files changed

+631
-101
lines changed

10 files changed

+631
-101
lines changed

.changeset/cold-pillows-protect.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': patch
3+
---
4+
5+
Fixes accessibility bugs in the Select component.

docs/content/Select.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {Select, Text} from '@primer/react'
1616
```jsx live
1717
<FormControl>
1818
<FormControl.Label>Preferred Primer component interface</FormControl.Label>
19-
<Select id="basic">
19+
<Select>
2020
<Select.Option value="figma">Figma</Select.Option>
2121
<Select.Option value="css">Primer CSS</Select.Option>
2222
<Select.Option value="prc">Primer React components</Select.Option>
@@ -30,7 +30,7 @@ import {Select, Text} from '@primer/react'
3030
```jsx live
3131
<FormControl>
3232
<FormControl.Label>Preferred Primer component interface</FormControl.Label>
33-
<Select id="grouped">
33+
<Select>
3434
<Select.OptGroup label="GUI">
3535
<Select.Option value="figma">Figma</Select.Option>
3636
</Select.OptGroup>
@@ -48,7 +48,7 @@ import {Select, Text} from '@primer/react'
4848
```jsx live
4949
<FormControl>
5050
<FormControl.Label>Preferred Primer component interface</FormControl.Label>
51-
<Select id="withPlaceholder" placeholder="Pick an interface">
51+
<Select placeholder="Pick an interface">
5252
<Select.Option value="figma">Figma</Select.Option>
5353
<Select.Option value="css">Primer CSS</Select.Option>
5454
<Select.Option value="prc">Primer React components</Select.Option>

src/Select.tsx

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React from 'react'
22
import styled from 'styled-components'
3-
import {get} from './constants'
43
import TextInputWrapper, {StyledWrapperProps} from './_TextInputWrapper'
54

65
export type SelectProps = Omit<
@@ -10,19 +9,35 @@ export type SelectProps = Omit<
109

1110
const StyledSelect = styled.select`
1211
appearance: none;
13-
background-color: transparent;
1412
border: 0;
1513
color: currentColor;
14+
font-size: inherit;
1615
outline: none;
1716
width: 100%;
1817
19-
option {
20-
color: initial;
18+
/* Firefox hacks: */
19+
/* 1. Makes Firefox's native dropdown menu's background match the theme.
20+
21+
background-color should be 'transparent', but Firefox uses the background-color on
22+
<select> to determine the background color used for the dropdown menu.
23+
*/
24+
background-color: inherit;
25+
26+
/* 2. Prevents visible overlap of partially transparent background colors.
27+
28+
'colors.input.disabledBg' happens to be partially transparent in light mode, so we use a
29+
transparent background-color on a disabled <select>. */
30+
&:disabled {
31+
background-color: transparent;
2132
}
2233
23-
/* colors the select input's placeholder text */
24-
&:invalid {
25-
color: ${get('colors.fg.subtle')};
34+
/* 3. Maintain dark bg color in Firefox on Windows high-contrast mode
35+
36+
Firefox makes the <select>'s background color white when setting 'background-color: transparent;' */
37+
@media screen and (forced-colors: active) {
38+
&:disabled {
39+
background-color: -moz-combobox;
40+
}
2641
}
2742
`
2843

@@ -44,18 +59,24 @@ const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
4459
({children, disabled, placeholder, size, required, validationStatus, ...rest}: SelectProps, ref) => (
4560
<TextInputWrapper
4661
sx={{
47-
position: 'relative'
62+
overflow: 'hidden',
63+
position: 'relative',
64+
'@media screen and (forced-colors: active)': {
65+
svg: {
66+
fill: disabled ? 'GrayText' : 'FieldText'
67+
}
68+
}
4869
}}
4970
size={size}
5071
validationStatus={validationStatus}
72+
disabled={disabled}
5173
>
5274
<StyledSelect
5375
ref={ref}
54-
required={required || Boolean(placeholder)}
76+
required={required}
5577
disabled={disabled}
56-
aria-required={required}
57-
aria-disabled={disabled}
5878
aria-invalid={validationStatus === 'error' ? 'true' : 'false'}
79+
data-hasplaceholder={Boolean(placeholder)}
5980
{...rest}
6081
>
6182
{placeholder && (

src/_TextInputWrapper.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,19 @@ export const TextInputBaseWrapper = styled.span<StyledBaseWrapperProps>`
9393
border-radius: ${get('radii.2')};
9494
outline: none;
9595
box-shadow: ${get('shadows.primer.shadow.inset')};
96-
cursor: text;
9796
display: inline-flex;
9897
align-items: stretch;
9998
min-height: 32px;
10099
100+
input,
101+
textarea {
102+
cursor: text;
103+
}
104+
105+
select {
106+
cursor: pointer;
107+
}
108+
101109
&::placeholder {
102110
color: ${get('colors.fg.subtle')};
103111
}
@@ -125,10 +133,15 @@ export const TextInputBaseWrapper = styled.span<StyledBaseWrapperProps>`
125133
${props =>
126134
props.disabled &&
127135
css`
128-
cursor: not-allowed;
129136
color: ${get('colors.primer.fg.disabled')};
130137
background-color: ${get('colors.input.disabledBg')};
131138
border-color: ${get('colors.border.default')};
139+
140+
input,
141+
textarea,
142+
select {
143+
cursor: not-allowed;
144+
}
132145
`}
133146
134147
${props =>

src/__tests__/Select.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('Select', () => {
7373
const placeholderOption = getByText('Pick a choice')
7474
const select = getByLabelText('Choice')
7575

76-
expect(select).not.toHaveAttribute('aria-required')
76+
expect(select).not.toHaveAttribute('required')
7777

7878
expect(placeholderOption).toBeDefined()
7979
expect(placeholderOption.tagName.toLowerCase()).toBe('option')
@@ -102,7 +102,7 @@ describe('Select', () => {
102102
const placeholderOption = getByText('Pick a choice')
103103
const select = getByLabelText('Choice')
104104

105-
expect(select).toHaveAttribute('aria-required')
105+
expect(select).toHaveAttribute('required')
106106

107107
expect(placeholderOption).toBeDefined()
108108
expect(placeholderOption.tagName.toLowerCase()).toBe('option')
@@ -130,7 +130,7 @@ describe('Select', () => {
130130

131131
const select = getByLabelText('Choice')
132132

133-
expect(select).toHaveAttribute('aria-disabled')
133+
expect(select).toHaveAttribute('disabled')
134134
expect(select).toHaveAttribute('disabled')
135135
})
136136
})

src/__tests__/__snapshots__/Autocomplete.test.tsx.snap

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ Array [
1212
border-radius: 6px;
1313
outline: none;
1414
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
15-
cursor: text;
1615
display: -webkit-inline-box;
1716
display: -webkit-inline-flex;
1817
display: -ms-inline-flexbox;
@@ -28,6 +27,15 @@ Array [
2827
padding-right: 0;
2928
}
3029
30+
.c0 input,
31+
.c0 textarea {
32+
cursor: text;
33+
}
34+
35+
.c0 select {
36+
cursor: pointer;
37+
}
38+
3139
.c0::-webkit-input-placeholder {
3240
color: #6e7781;
3341
}
@@ -163,7 +171,6 @@ Array [
163171
border-radius: 6px;
164172
outline: none;
165173
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
166-
cursor: text;
167174
display: -webkit-inline-box;
168175
display: -webkit-inline-flex;
169176
display: -ms-inline-flexbox;
@@ -179,6 +186,15 @@ Array [
179186
padding-right: 0;
180187
}
181188
189+
.c0 input,
190+
.c0 textarea {
191+
cursor: text;
192+
}
193+
194+
.c0 select {
195+
cursor: pointer;
196+
}
197+
182198
.c0::-webkit-input-placeholder {
183199
color: #6e7781;
184200
}
@@ -349,7 +365,6 @@ Array [
349365
border-radius: 6px;
350366
outline: none;
351367
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
352-
cursor: text;
353368
display: -webkit-inline-box;
354369
display: -webkit-inline-flex;
355370
display: -ms-inline-flexbox;
@@ -365,6 +380,15 @@ Array [
365380
padding-right: 0;
366381
}
367382
383+
.c0 input,
384+
.c0 textarea {
385+
cursor: text;
386+
}
387+
388+
.c0 select {
389+
cursor: pointer;
390+
}
391+
368392
.c0::-webkit-input-placeholder {
369393
color: #6e7781;
370394
}
@@ -1298,7 +1322,6 @@ Array [
12981322
border-radius: 6px;
12991323
outline: none;
13001324
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
1301-
cursor: text;
13021325
display: -webkit-inline-box;
13031326
display: -webkit-inline-flex;
13041327
display: -ms-inline-flexbox;
@@ -1314,6 +1337,15 @@ Array [
13141337
padding-right: 0;
13151338
}
13161339
1340+
.c0 input,
1341+
.c0 textarea {
1342+
cursor: text;
1343+
}
1344+
1345+
.c0 select {
1346+
cursor: pointer;
1347+
}
1348+
13171349
.c0::-webkit-input-placeholder {
13181350
color: #6e7781;
13191351
}
@@ -2157,7 +2189,6 @@ Array [
21572189
border-radius: 6px;
21582190
outline: none;
21592191
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
2160-
cursor: text;
21612192
display: -webkit-inline-box;
21622193
display: -webkit-inline-flex;
21632194
display: -ms-inline-flexbox;
@@ -2173,6 +2204,15 @@ Array [
21732204
padding-right: 0;
21742205
}
21752206
2207+
.c0 input,
2208+
.c0 textarea {
2209+
cursor: text;
2210+
}
2211+
2212+
.c0 select {
2213+
cursor: pointer;
2214+
}
2215+
21762216
.c0::-webkit-input-placeholder {
21772217
color: #6e7781;
21782218
}
@@ -3027,7 +3067,6 @@ Array [
30273067
border-radius: 6px;
30283068
outline: none;
30293069
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
3030-
cursor: text;
30313070
display: -webkit-inline-box;
30323071
display: -webkit-inline-flex;
30333072
display: -ms-inline-flexbox;
@@ -3043,6 +3082,15 @@ Array [
30433082
padding-right: 0;
30443083
}
30453084
3085+
.c0 input,
3086+
.c0 textarea {
3087+
cursor: text;
3088+
}
3089+
3090+
.c0 select {
3091+
cursor: pointer;
3092+
}
3093+
30463094
.c0::-webkit-input-placeholder {
30473095
color: #6e7781;
30483096
}
@@ -4027,7 +4075,6 @@ Array [
40274075
border-radius: 6px;
40284076
outline: none;
40294077
box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
4030-
cursor: text;
40314078
display: -webkit-inline-box;
40324079
display: -webkit-inline-flex;
40334080
display: -ms-inline-flexbox;
@@ -4043,6 +4090,15 @@ Array [
40434090
padding-right: 0;
40444091
}
40454092
4093+
.c0 input,
4094+
.c0 textarea {
4095+
cursor: text;
4096+
}
4097+
4098+
.c0 select {
4099+
cursor: pointer;
4100+
}
4101+
40464102
.c0::-webkit-input-placeholder {
40474103
color: #6e7781;
40484104
}

0 commit comments

Comments
 (0)