Tab selection ignores all elements generated within loop in React

Viewed 55

I have radio group in my application and this group builds based on array const genders = ["Male", "Female", "Neutral"];, when I press the Tab button focus falls at the first element only and goes further ignoring the rest of elements.

const { useState } = React;

const App = () => {
  const genders = ["male", "female", "netural"];
  return (
    <React.Fragment>
      <h1>Hello Example</h1>
      <div>
        {genders.map((gender) => (
          <div key={gender}>
            <div>
              <input tabIndex={0} type="radio" value={gender} />
              {gender}
            </div>
          </div>
        ))}
      </div>
    </React.Fragment>
  );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

I have prepared working example and sahred it here: sandbox

I have tried to add tabIndex to parent and to input element, but it didn't work. When I add tabIndex={0} it selects parent element first and goes to the input element after clicking the Tab one more time, it is bad behaviour.

Please tell me how can it be fixed ?

3 Answers

Just add a name to your inputs.

<input name={"gender-" + gender} type="radio" value={gender} />

Full example:

const App = () => {
  const genders = ["male", "female", "netural"];
  return (
    <React.Fragment>
      <h1>Hello Example</h1>
      <div>
        <p>
          Same name (and same group, only 1 selected) - select first element with TAB and use arrows up and down
          keys to navigate
        </p>
        {genders.map((gender) => (
          <div key={gender}>
            <div>
              <input name={"gender"} type="radio" value={gender} />
              {gender}
            </div>
          </div>
        ))}
      </div>
      <br />
      <div>
        <p>Different names (and group - you can select all of them) - use tab key to navigate</p>
        {genders.map((gender) => (
          <div key={gender}>
            <div>
              <input name={"gender" + gender} type="radio" value={gender} />
              {gender}
            </div>
          </div>
        ))}
      </div>
    </React.Fragment>
  );
};

ReactDOM.render(
    <App />,
    document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Notes: https://stackoverflow.com/a/14322638/5767872

Radio buttons with one name is like single control (like input or select), you can change it value with arrow keys, tab key moves focus to another control.

(Thanks @t-j-crowder)

I think that you need kind of radio group behavior (only one selected from the set). You may add the same attr name for all inputs and that TAB will focus the first one. To navigate to the next you may use arrows key (actually navigate and select).

For example, check - https://mui.com/material-ui/react-radio-button/.

There are really two problems:

  1. Keyboard navigation isn't working as you expect.

  2. The radio buttons don't form a radio group. Each of them is separate, and they can all be selected at the same time.

The keyboard navigation thing is largely a misunderstanding: You can't tab between the choices, because the set of elements is logically a single field (even though they're not acting like it right now). Instead, to use keyboard navigation, you tab to the group, and the select the options via the Up/Down and Right/Left keys.

The issue with their not being a true group can be corrected by giving the radio buttons the same name (for instance, make them all name="gender"). Then they're mutually-exclusive, and they're properly in a group (which is important for assistive technologies).

const { useState } = React;

const App = () => {
    const genders = ["male", "female", "netural"];
    return (
        <React.Fragment>
            <h1>Hello Example</h1>
            <div>
                {genders.map((gender) => (
                    <div key={gender}>
                        <div>
                            <input name="gender" tabIndex={0} type="radio" value={gender} />
                            {gender}
                        </div>
                    </div>
                ))}
            </div>
        </React.Fragment>
    );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

To further enable assistive technologies, ensure that each radio button is labelled:

const { useState } = React;

const App = () => {
    const genders = ["male", "female", "neutral"];
    return (
        <React.Fragment>
            <h1>Hello Example</h1>
            <div>
                {genders.map((gender) => (
                    <div key={gender}>
                        <div>
                            <label>
                                <input name="gender" tabIndex={0} type="radio" value={gender} />
                                {gender}
                            </label>
                        </div>
                    </div>
                ))}
            </div>
        </React.Fragment>
    );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

You might also add a fieldset around the group with a legend. (And put the whole thing in a form.)

Related