Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ARIA] Add more Accessible Tags in Input and MenuList #4322

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 10 additions & 1 deletion packages/react-select/src/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,7 @@ export default class Select extends Component<Props, State> {
const custom = this.props.styles[key];
return custom ? custom(base, props) : base;
};
getElementId = (element: 'group' | 'input' | 'listbox' | 'option') => {
getElementId = (element: 'group' | 'input' | 'listbox' | 'combobox' | 'option') => {
0xTheProDev marked this conversation as resolved.
Show resolved Hide resolved
return `${this.instancePrefix}-${element}`;
};
getActiveDescendentId = () => {
Expand Down Expand Up @@ -1421,9 +1421,13 @@ export default class Select extends Component<Props, State> {
const { inputIsHidden } = this.state;

const id = inputId || this.getElementId('input');
const menuRole = isSearchable ? 'combobox' : 'listbox';
const menuId = this.getElementId(menuRole);

// aria attributes makes the JSX "noisy", separated for clarity
const ariaAttributes = {
role: menuRole,
'aria-owns': menuId,
'aria-autocomplete': 'list',
'aria-label': this.props['aria-label'],
'aria-labelledby': this.props['aria-labelledby'],
Expand Down Expand Up @@ -1668,6 +1672,7 @@ export default class Select extends Component<Props, State> {
noOptionsMessage,
onMenuScrollToTop,
onMenuScrollToBottom,
isSearchable,
} = this.props;

if (!menuIsOpen) return null;
Expand Down Expand Up @@ -1728,6 +1733,9 @@ export default class Select extends Component<Props, State> {
menuShouldScrollIntoView,
};

const menuRole = isSearchable ? 'combobox' : 'listbox';
const menuId = this.getElementId(menuRole);

const menuElement = (
<MenuPlacer {...commonProps} {...menuPlacementProps}>
{({ ref, placerProps: { placement, maxHeight } }) => (
Expand All @@ -1750,6 +1758,7 @@ export default class Select extends Component<Props, State> {
<ScrollBlock isEnabled={menuShouldBlockScroll}>
<MenuList
{...commonProps}
id={menuId}
innerRef={this.getMenuListRef}
isLoading={isLoading}
maxHeight={maxHeight}
Expand Down
5 changes: 5 additions & 0 deletions packages/react-select/src/__tests__/Select.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ test('snapshot - defaults', () => {
expect(container).toMatchSnapshot();
});

test('snapshot - listbox', () => {
const { container } = render(<Select isSearchable={false} />);
expect(container).toMatchSnapshot();
});

test('instanceId prop > to have instanceId as id prefix for the select components', () => {
let { container } = render(
<Select {...BASIC_PROPS} menuIsOpen instanceId={'custom-id'} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,12 @@ exports[`defaults - snapshot 1`] = `
>
<input
aria-autocomplete="list"
aria-owns="react-select-2-combobox"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,12 @@ exports[`defaults - snapshot 1`] = `
>
<input
aria-autocomplete="list"
aria-owns="react-select-2-combobox"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,12 @@ exports[`defaults - snapshot 1`] = `
>
<input
aria-autocomplete="list"
aria-owns="react-select-2-combobox"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
Expand Down
200 changes: 200 additions & 0 deletions packages/react-select/src/__tests__/__snapshots__/Select.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,12 @@ exports[`snapshot - defaults 1`] = `
>
<input
aria-autocomplete="list"
aria-owns="react-select-2-combobox"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
Expand Down Expand Up @@ -204,3 +206,201 @@ exports[`snapshot - defaults 1`] = `
</div>
</div>
`;

exports[`snapshot - listbox 1`] = `
.emotion-8 {
position: relative;
box-sizing: border-box;
}

.emotion-7 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background-color: hsl(0,0%,100%);
border-color: hsl(0,0%,80%);
border-radius: 4px;
border-style: solid;
border-width: 1px;
cursor: default;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
min-height: 38px;
outline: 0 !important;
position: relative;
-webkit-transition: all 100ms;
transition: all 100ms;
box-sizing: border-box;
}

.emotion-7:hover {
border-color: hsl(0,0%,70%);
}

.emotion-2 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
padding: 2px 8px;
-webkit-overflow-scrolling: touch;
position: relative;
overflow: hidden;
box-sizing: border-box;
}

.emotion-0 {
color: hsl(0,0%,50%);
margin-left: 2px;
margin-right: 2px;
position: absolute;
top: 50%;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
box-sizing: border-box;
}

.emotion-6 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-align-self: stretch;
-ms-flex-item-align: stretch;
align-self: stretch;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
box-sizing: border-box;
}

.emotion-3 {
-webkit-align-self: stretch;
-ms-flex-item-align: stretch;
align-self: stretch;
background-color: hsl(0,0%,80%);
margin-bottom: 8px;
margin-top: 8px;
width: 1px;
box-sizing: border-box;
}

.emotion-5 {
color: hsl(0,0%,80%);
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 8px;
-webkit-transition: color 150ms;
transition: color 150ms;
box-sizing: border-box;
}

.emotion-5:hover {
color: hsl(0,0%,60%);
}

.emotion-4 {
display: inline-block;
fill: currentColor;
line-height: 1;
stroke: currentColor;
stroke-width: 0;
}

.emotion-1 {
background: 0;
border: 0;
font-size: inherit;
outline: 0;
padding: 0;
width: 1px;
color: transparent;
left: -100px;
opacity: 0;
position: relative;
-webkit-transform: scale(0);
-ms-transform: scale(0);
transform: scale(0);
}

<div>
<div
class=" emotion-8"
>
<div
class=" emotion-7"
>
<div
class=" emotion-2"
>
<div
class=" emotion-0"
>
Select...
</div>
<input
aria-autocomplete="list"
aria-owns="react-select-3-listbox"
class="emotion-1"
id="react-select-3-input"
readonly=""
role="listbox"
tabindex="0"
value=""
/>
</div>
<div
class=" emotion-6"
>
<span
class=" emotion-3"
/>
<div
aria-hidden="true"
class=" emotion-5"
>
<svg
aria-hidden="true"
class="emotion-4"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,12 @@ exports[`defaults > snapshot 1`] = `
>
<input
aria-autocomplete="list"
aria-owns="react-select-2-combobox"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
Expand Down
15 changes: 13 additions & 2 deletions packages/react-select/src/components/Menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,10 @@ export type MenuListProps = {
children: Node,
/** Inner ref to DOM Node */
innerRef: InnerRef,
/** Props to be passed to the menu-list wrapper. */
/** Props to be passed to the menu-list wrapper. */
innerProps: {},
/** ID of DOM Element */
id?: string,
};
export type MenuListComponentProps = CommonProps &
MenuListProps &
Expand All @@ -370,9 +372,17 @@ export const menuListCSS = ({
WebkitOverflowScrolling: 'touch',
});
export const MenuList = (props: MenuListComponentProps) => {
const { children, className, cx, getStyles, isMulti, innerRef, innerProps } = props;
const { children, className, cx, getStyles, isMulti, innerRef, innerProps, id } = props;

// aria attributes makes the JSX "noisy", separated for clarity
const ariaAttributes = {
role: 'listbox',
ebonow marked this conversation as resolved.
Show resolved Hide resolved
'aria-expanded': true,
};

return (
<div
id={id}
css={getStyles('menuList', props)}
className={cx(
{
Expand All @@ -383,6 +393,7 @@ export const MenuList = (props: MenuListComponentProps) => {
)}
ref={innerRef}
{...innerProps}
{...ariaAttributes}
0xTheProDev marked this conversation as resolved.
Show resolved Hide resolved
>
{children}
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/react-select/src/components/Option.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const Option = (props: OptionProps) => {
} = props;
return (
<div
role="option"
0xTheProDev marked this conversation as resolved.
Show resolved Hide resolved
css={getStyles('option', props)}
className={cx(
{
Expand Down