How to make the options in react-select dropdown accessible? - reactjs

I am building a reactjs application and I am using a library called react-select for my dropdown which is searchable.
but the problem I am facing is that the options inside the select are not being read out by NVDA screenreader when using arrow keys.
and am not able to set focus on this dropdown as well for some reason.
I tried it via the official documentation but no luck as of now.
The library I am using:
React-select
https://react-select.com/home
The code:
import React, { Component, Fragment } from "react";
import Select from "react-select";
export const flavourOptions = [
{ value: "vanilla", label: "Vanilla", rating: "safe" },
{ value: "chocolate", label: "Chocolate", rating: "good" },
{ value: "strawberry", label: "Strawberry", rating: "wild" },
{ value: "salted-caramel", label: "Salted Caramel", rating: "crazy" }
];
export default class SampleDropdown extends Component {
state = {
isClearable: true,
isDisabled: false,
isLoading: false,
isRtl: false,
isSearchable: true
};
componentDidMount() {
document.getElementById("translate").focus();
}
render() {
const {
isClearable,
isSearchable,
isDisabled,
isLoading,
isRtl
} = this.state;
return (
<Fragment>
<Select
className="basic-single"
classNamePrefix="select"
defaultValue={flavourOptions[0]}
isDisabled={isDisabled}
isLoading={isLoading}
isClearable={isClearable}
isRtl={isRtl}
isSearchable={isSearchable}
name="color"
options={flavourOptions}
id="translate"
/>
</Fragment>
);
}
}
And here is a working example in codesandbox.
https://codesandbox.io/s/focused-clarke-euk0e
Actual result: When I enter the page, the dropdown does not have the focus. and am not able to read out options in the dropdown using arrow keys in NVDA screenreader.the options are being read out as blank.
Expected result: When I enter the page, the dropdown should have the focus. and the options in the dropdown should be read out when using arrow keys when NVDA screenreader is switched on.

I looked at using the same library but ran into accessibility issues as well. I ended up building my custom select element and manually handling the key presses, focus movement, and label announcements. If you're stuck on using react-select you'll probably need to amend it yourself or wait for a PR.
Otherwise, if you're up for the challenge, you can follow my tutorial on creating an accessible select component in React. You can pull apart the code on codesandbox as well. This might make it easier to port to the react-select as well.
And of course, I'd also recommend using the native select element, as that will handle accessibility best.

Reach UI has accessible components. This Combobox could be of use https://reach.tech/combobox

Related

Hide current value on focus of react-select

When a user sets focus on the edit box of a single-selection react-select component, the currently-selected option will continue to be shown until the user types a key. I don't like this behavior. Instead I'd like do to what Google does which is to clear the edit box on focus.
How to do this?
I noticed there's a long-open issue in the react-select GitHub repo, but the solutions on that issue seem either complex or have UX tradeoffs that I'd like to avoid.
It took some experimentation, but the easiest solution was creating a custom SingleValue component that renders no content when the menu is open. Posting the answer here to save the time of others who may run into the same problem.
Note that the text shown isn't actually in the edit box. It's a separate HTML element of static text that's hidden by the library when the user types. I'm just accelerating that process. :-)
Live example
import { Fragment, useState } from "react";
import Select, { components } from "react-select";
const options = [
{ label: "one", value: 1 },
{ label: "two", value: 2 },
{ label: "three", value: 3 }
];
export default function App() {
const [selectedOption, setSelectedOption] = useState(options[1]);
return (
<Select
options={options}
components={{ SingleValue }}
value={selectedOption}
onChange={setSelectedOption}
placeholder="Pick a number"
/>
);
}
/** Hide the selected value when showing the dropdown list */
function SingleValue(props) {
const { children, ...rest } = props;
const { selectProps } = props;
if (selectProps.menuIsOpen) return <Fragment></Fragment>;
return <components.SingleValue {...rest}>{children}</components.SingleValue>;
}
// For TypeScript, declare like this:
// function SingleValue<T>(props: SingleValueProps<T, false>) {
If you want to show a placeholder instead of a blank edit box, you can just return your placeholder in place of children. Like this: (live example)
/** Show a placeholder instead of the selected value when showing the dropdown list */
function SingleValue(props) {
const { children, ...rest } = props;
const { selectProps } = props;
let contents = children;
if (selectProps.menuIsOpen) {
contents = selectProps.placeholder ? (
<div style={{ color: "#999" }}>{selectProps.placeholder}</div>
) : (
<Fragment></Fragment>
);
}
return <components.SingleValue {...rest}>{contents}</components.SingleValue>;
}
It's a shame that the docs for each react-select prop are so limited and they lack a table of contents to easily navigate. It's really full-featured once you figure out how the advanced stuff works like replacing custom components. If you're considering contributing feature PRs to that library, consider enhancing the docs too!

How do I trigger the change event on a react-select component with react-testing-library?

Given that I can't test internals directly with react-testing-library, how would I go about testing a component that uses react-select? For instance, if I have a conditional render based on the value of the react-select, which doesn't render a traditional <select/>, can I still trigger the change?
import React, { useState } from "react";
import Select from "react-select";
const options = [
{ value: "First", label: "First" },
{ value: "Second", label: "Second" },
{ value: "Third", label: "Third" },
];
function TestApp() {
const [option, setOption] = useState(null);
return (
<div>
<label htmlFor="option-select">Select Option</label>
<Select
value={option}
options={options}
onChange={option => setOption(option)}
/>
{option && <div>{option.label}</div>}
</div>
);
}
export default TestApp;
I'm not even sure what I should query for. Is it the hidden input?
My team has a test utility in our project that lets us select an item easily after spending too much time trying to figure out how to do this properly. Sharing it here to hopefully help others.
This doesn't rely on any React Select internals or mocking but does require you to have set up a <label> which has a for linking to the React Select input. It uses the label to select a given choice value just like a user would on the real page.
const KEY_DOWN = 40
// Select an item from a React Select dropdown given a label and
// choice label you wish to pick.
export async function selectItem(
container: HTMLElement,
label: string,
choice: string
): Promise<void> {
// Focus and enable the dropdown of options.
fireEvent.focus(getByLabelText(container, label))
fireEvent.keyDown(getByLabelText(container, label), {
keyCode: KEY_DOWN,
})
// Wait for the dropdown of options to be drawn.
await findByText(container, choice)
// Select the item we care about.
fireEvent.click(getByText(container, choice))
// Wait for your choice to be set as the input value.
await findByDisplayValue(container, choice)
}
It can be used like this:
it('selects an item', async () => {
const { container } = render(<MyComponent/>)
await selectItem(container, 'My label', 'value')
})
You can try the following to get it working:
Fire focus event on the ReactSelect component .react-select input element.
Fire a mouseDown event on the .react-select__control element
Fire a click on the option element that you want to select
You can add a className and classNamePrefix props with the value of "react-select" in order to specifically select the component you are trying to test.
PS: In case you are still stuck I'd encourage you to take a look at this conversation from where the above answer is borrowed - https://spectrum.chat/react-testing-library/general/testing-react-select~5857bb70-b3b9-41a7-9991-83f782377581

how to make a message without wrapping in span in react-intl

i have a problem with yahoo/react-intl thats i want to make messages in string type but when im using FormattedMessage it gives me message wrapped in span and thats not cool.
i tried formatMessage and that not working too.
i be very thankful for any help or advise this is my code:
import React from 'react';
import {FormattedMessage} from 'react-intl';
export default {
items: [
{
name: <FormattedMessage id='app.dashboard'/>,
url: '/dashboard',
icon: 'icon-speedometer',
badge: {
variant: 'info',
text: 'New',
},
},
{
title: true,
name: <FormattedMessage id='app.dashboard'/>,
// optional wrapper object
wrapper: {
// required valid HTML5 element tag
element: 'strong',
// optional valid JS object with JS API naming ex: { className: "my-class", style: { fontFamily: "Verdana" }, id: "my-id"}
attributes: {},
},
// optional class names space delimited list for title item ex: "text-center"
class: '',`enter code here`
},
for use in jsx:
it's rendered as a <span>:
<FormattedMessage id='app.dashboard'/>
it's rendered as an <option>:
<FormattedMessage id='app.dashboard' children={msg=> <option>{msg}</option>}/>
or:
<FormattedMessage id='app.dashboard' tagName="option"/>
it's rendered to nothing:
<FormattedMessage id='app.dashboard' children={msg=> <>{msg}</>}/>
or:
<FormattedMessage id="app.dashboard">{txt => txt}</FormattedMessage>
To use it in a component, you can use formatMessage() like this:
const App=()=>{
const value = intl.formatMessage({ id: 'header.nav.login' });
return(<div>{value}</>)
}
Given that you inject the intl context by yourself, Then you can use the formatMessage function.
For example, in your case:
items: [
{
name: intl.formatMessage({id:'app.dashboard'});
}
]
To get intl in your component you have two choices:
get it from your component's context
use injectIntl to get it in your props.
If you're not in a component, it gets slightly harder but I would just put the id instead of the formatted message in name and then use the react-intl context when available. Here, in the component that consumes and displays this list of items.
The solution here is to upgrade react-intl to version 3.
In version 3, the <FormattedMesage> (and similarly others react-intl components) is rendering into React.Fragment.
If you want to render it to something else you can specify textComponent prop on IntlProvider, eg.:
<IntlProvider textComponent="span" />
See info in Migration Guide (v2 -> v3).

react-select: Do we need CSS as well just to display a normal select list?

I have following code which displays a normal select list. It is working fine logically and i am able to log when any change happens. But the problem is, very small box is being displayed instead of a text box which is adequate to show the options.And my placeholder is being displayed as a normal text. But when i click on placeholder, all values are being displayed.
import Select from 'react-select;
class SelectList extends Component {
onChange() {
console.log("Value Changed");
}
render() {
var Select = require('react-select');
var options = [
{ value: 'one', label: 'India' },
{ value: 'two', label: 'Singapore' },
{ value: 'three', label: 'IceLand' }
];
return(
<Select
name="Select List"
value="one"
options={options}
onChange={this.onChange.this(bind}}
/>
);
}
do I need any CSS things here. Could anybody let me know what am I missing here?
[1]: https://i.stack.imgur.com/NA4hT.png
You're already importing Select at the top, why are you doing
var Select = require('react-select');
again in the render function?
Also your onChange handler should be onChange={this.onChange} and you can bind the handler at the top in the constructor like this:
this.onChange = this.onChange.bind(this);
Or you can pass it to the Select component like this
onChange={() => {this.onChange()}}
As for the CSS issue, from github:
// Be sure to include styles at some point, probably during your bootstrapping
import 'react-select/dist/react-select.css';

React Material UI SelectField menu items not appearing

Using Material-UI 0.15.1, React 15.2.0
When I click on the SelectField dropdown I get an empty dropdown appearing with out the menu choices appearing. When I use hardcoded MenuItems in the SelectField I see the full menu without issue.
This does not appear to be an injectTapEventPlugin as I am importing it and calling it. My code is below:
render() {
var divStyle = {
fontSize: 16
};
var mymenuItems = [
{ payload: '1', text: 'one' },
{ payload: '2', text: 'two' },
{ payload: '3', text: 'three' },
{ payload: '4', text: 'four' },
{ payload: '5', text: 'five' },
];
return (
<div style={divStyle}>
<SelectField
value={this.state.selected}
onChange={this._onSelect}
floatingLabelText="Product"
menuItems={mymenuItems}>
</SelectField>
</div>
)
}
I am also getting a
Warning: Unknown props onItemTouchTap, disableAutoFocus,
onEscKeyDown on tag. Remove these props from the element
in the console when I click on the SelectField but I saw others had similar issues due to new React version it and it appears people think it should not affect my code (though it is very annoying)
Not sure why you are using menuItems, this property is from old material-ui versions.
But its simple to fix - based on your code you can just map through the array and return MenuItem elements..
Example:
<SelectField
value={this.state.selected}
onChange={this._onSelect}
floatingLabelText="Product">
{mymenuItems.map(x => <MenuItem key={x.payload} value={x.payload} primaryText={x.text} />)}
</SelectField>
I'd suggest you to check the examples on the material-ui docs
http://www.material-ui.com/#/components/select-field
Try putting this in your build:
var injectTapEventPlugin = require("react-tap-event-plugin");
injectTapEventPlugin();
Make sure that function is ran before you render anything to page.
Reference: https://github.com/callemall/material-ui/issues/1011

Resources