Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
44 changes: 34 additions & 10 deletions src/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,38 @@ 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;
}

/* colors the select input's placeholder text */
&:invalid {
&[data-hasplaceholder='true'] {
color: ${get('colors.fg.subtle')};
}

/* Firefox hacks: */
/* 1. Reverts color of non-placeholder options in the dropdown */
&[data-hasplaceholder='true'] option:not(:first-child):not(:disabled),
optgroup:not(:disabled) {
color: ${get('colors.fg.default')};
}

/* 2. 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;

/* 3. 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;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Help wanted: I don't like this solution, but I've yet to come up with anything better.

`

const ArrowIndicatorSVG: React.FC<{className?: string}> = ({className}) => (
Expand All @@ -44,18 +62,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 (-ms-high-contrast: 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