diff --git a/packages/react-select/src/Select.js b/packages/react-select/src/Select.js index c6cf12ae79..7f4c0efa4d 100644 --- a/packages/react-select/src/Select.js +++ b/packages/react-select/src/Select.js @@ -806,7 +806,7 @@ export default class Select extends Component { const custom = this.props.styles[key]; return custom ? custom(base, props) : base; }; - getElementId = (element: 'group' | 'input' | 'listbox' | 'option') => { + getElementId = (element: 'group' | 'input' | 'menu' | 'option') => { return `${this.instancePrefix}-${element}`; }; getActiveDescendentId = () => { @@ -1421,9 +1421,13 @@ export default class Select extends Component { const { inputIsHidden } = this.state; const id = inputId || this.getElementId('input'); + const menuRole = isSearchable ? 'combobox' : 'listbox'; + const menuId = this.getElementId('menu'); // 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'], @@ -1728,6 +1732,8 @@ export default class Select extends Component { menuShouldScrollIntoView, }; + const menuId = this.getElementId('menu'); + const menuElement = ( {({ ref, placerProps: { placement, maxHeight } }) => ( @@ -1750,6 +1756,7 @@ export default class Select extends Component { { expect(container).toMatchSnapshot(); }); +test('snapshot - listbox', () => { + const { container } = render( diff --git a/packages/react-select/src/__tests__/__snapshots__/Async.test.js.snap b/packages/react-select/src/__tests__/__snapshots__/Async.test.js.snap index 23814a323b..3584ea3697 100644 --- a/packages/react-select/src/__tests__/__snapshots__/Async.test.js.snap +++ b/packages/react-select/src/__tests__/__snapshots__/Async.test.js.snap @@ -160,10 +160,12 @@ exports[`defaults - snapshot 1`] = ` > `; + +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); +} + +
+
+
+
+
+ Select... +
+ +
+
+ + +
+
+
+
+`; diff --git a/packages/react-select/src/__tests__/__snapshots__/StateManaged.test.js.snap b/packages/react-select/src/__tests__/__snapshots__/StateManaged.test.js.snap index 64ab356056..507c7dd1f2 100644 --- a/packages/react-select/src/__tests__/__snapshots__/StateManaged.test.js.snap +++ b/packages/react-select/src/__tests__/__snapshots__/StateManaged.test.js.snap @@ -160,10 +160,12 @@ exports[`defaults > snapshot 1`] = ` > { - 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', + 'aria-expanded': true, + }; + return (
{ className )} ref={innerRef} + {...ariaAttributes} {...innerProps} > {children} diff --git a/packages/react-select/src/components/Option.js b/packages/react-select/src/components/Option.js index de4eeff676..8cf245a9cc 100644 --- a/packages/react-select/src/components/Option.js +++ b/packages/react-select/src/components/Option.js @@ -84,6 +84,8 @@ const Option = (props: OptionProps) => { } = props; return (