HOWTO · React
How to Implement Radio Button in React
This article introduces how to implement a radio button in React.
On this page
Radio buttons let a user choose one value from a small set. In React, give every option in the same group a shared name, a distinct value, and a readable label. The examples below use theme as the group name and DARK and LIGHT as the submitted values. The visible label and the value sent to your handler do not have to use the same text.
Group Radio Inputs with a Shared Name
In JSX, a group can be written as two <input type="radio" name="theme" value="DARK" /> and <input type="radio" name="theme" value="LIGHT" /> elements, each wrapped in a <label> or paired with a <label htmlFor="...">. The shared name makes the browser treat the inputs as one mutually exclusive choice; distinct values tell the application which choice was selected. Give each input a unique id when using htmlFor, and ensure each label names its own option. This keeps the controls understandable to screen readers and clickable through their labels.
Handle a Selection with React State
For an interactive form, keep the selected value in React state and update it with an onChange handler. The stateful JSX examples assume useState is imported from React. This complete function component initializes theme to LIGHT, gives both labeled inputs the shared theme name, and derives each checked prop from state: function ThemePicker() { const [theme, setTheme] = useState("LIGHT"); return (<fieldset><legend>Theme</legend>{[{ label: "Dark", value: "DARK" }, { label: "Light", value: "LIGHT" }].map((option) => (<label key={option.value}><input type="radio" name="theme" value={option.value} checked={theme === option.value} onChange={(event) => setTheme(event.target.value)} />{option.label}</label>))}</fieldset>); }. When a user selects Dark, the event’s target.value is the string DARK; setTheme stores that value, and the next render checks the matching radio. The visible label and submitted value can differ.
React’s change event bubbles from the selected input. onSubmit handles form submission, while a button’s onClick handles a button action, so neither replaces onChange for tracking a radio selection. When a user selects either option, the onChange handler reads its string value from event.target.value and stores that string in theme. The PlayCode demonstration is an optional interactive companion.
The diagram follows the selection from the input’s value into state; it is an instructional diagram, not a screenshot.
Run this example:
const options = [
{ label: "Dark", value: "DARK" },
{ label: "Light", value: "LIGHT" },
];
let theme = "light";
const handleChange = (event) => {
theme = event.target.value;
};
handleChange({ target: { value: options[0].value } });
console.log(`Selected theme: ${theme}`);
Selected theme: DARK
This standalone JavaScript example models the value passed from a selected input to a state update. In a React component, useState supplies the setter and a checked prop connects that state back to the rendered controls.
Render Options from an Array
For a longer list, keep choices in an array and use .map() in JSX to produce one labeled input per option. Here is a complete list-rendering component: const options = [{ label: "Blue", value: "blue" }, { label: "Green", value: "green" }]; function ColorOptions() { return (<fieldset><legend>Color</legend>{options.map((option) => (<label key={option.value}><input type="radio" name="color" value={option.value} />{option.label}</label>))}</fieldset>); }. The array supplies each label and distinct value; the mapping returns a label and input for every choice. The key is on the outer label returned by .map(), allowing React to track each item as the list changes. Both inputs share name="color", so the browser treats them as one mutually exclusive group. The JavaScript expression inside JSX braces evaluates the mapping and inserts its returned elements into the page.
Choose an Initial or Controlled Selection
Use defaultChecked to initialize an uncontrolled group when React does not need to own later changes. For example, a complete two-option group can be written as function InitialTheme() { return (<fieldset><legend>Theme</legend><label><input type="radio" name="theme" value="DARK" defaultChecked />Dark</label><label><input type="radio" name="theme" value="LIGHT" />Light</label></fieldset>); }. The browser manages later choices, and only the intended starting option receives defaultChecked.
For a controlled group, store the selection in state, set each input’s checked prop by comparing the state with its value, and update state in onChange: function ControlledTheme() { const [theme, setTheme] = useState("DARK"); return (<fieldset><legend>Theme</legend><label><input type="radio" name="theme" value="DARK" checked={theme === "DARK"} onChange={(event) => setTheme(event.target.value)} />Dark</label><label><input type="radio" name="theme" value="LIGHT" checked={theme === "LIGHT"} onChange={(event) => setTheme(event.target.value)} />Light</label></fieldset>); }. The state-derived checked values keep the rendered choice synchronized with React. Choose either the uncontrolled initial-selection pattern or the controlled pattern; do not combine defaultChecked and checked on one input.
Extract a Reusable Radio Group Component
When several forms need the same behavior, extract a component that receives name, options, the selected value, and an onChange callback. This implementation maps options to labeled inputs, uses each option value as a stable key, and derives checked from the selected value: function RadioGroup({ name, options, value, onChange }) { return (<fieldset><legend>{name}</legend>{options.map((option) => (<label key={option.value}><input type="radio" name={name} value={option.value} checked={value === option.value} onChange={(event) => onChange(event.target.value)} />{option.label}</label>))}</fieldset>); }. The parent owns state and passes its setter as the callback: function ThemeForm() { const [theme, setTheme] = useState("LIGHT"); const options = [{ label: "Dark", value: "DARK" }, { label: "Light", value: "LIGHT" }]; return <RadioGroup name="theme" options={options} value={theme} onChange={setTheme} />; }. The shared name keeps the controls mutually exclusive, labels stay associated with inputs, and the component does not create a second copy of the selection state. The same component can render themes, colors, or other choices by changing its props.