React Radio Buttons
+ −
In the last lesson on React Select Dropdowns you let the user pick one option from a hidden menu. Radio buttons do the same “pick ONE” job, but they show every option on screen at once, and here you’ll wire them to state as controlled components.
🤔 Why do radio buttons feel tricky?
A radio button isn’t a single box like a text input. It’s a group, and the group has to behave as one unit.
- A radio group is many small circles, but only ONE can be filled at a time.
- The browser tracks which one is picked for you, but React wants YOUR state to be the single source of truth.
- So if you leave it to the browser, your component has no idea what the user chose, and you can’t use that value later.
So the fix is to make the group controlled, which means state decides which radio is filled, not the browser.
🧩 What makes a radio group work?
A radio group is just a few <input type="radio"> tags that belong together, tied into one group by a shared name.
- Every radio in the same group shares the same
name. That’s how the browser knows “these belong together, only one can win”. - Each radio has its own unique
value. That’s the actual answer, like “free” or “pro”. - You add a label next to each one so the user knows what they’re picking.
Here’s the plain HTML shape of a plan picker, before we bring React in.
<label> <input type="radio" name="plan" value="free" /> Free</label><label> <input type="radio" name="plan" value="pro" /> Pro</label><label> <input type="radio" name="plan" value="team" /> Team</label>The shared name="plan" is doing the grouping. Because all three share it, clicking one turns the others off automatically.
🎛️ Controlling a radio with checked
Now the React part. A controlled radio doesn’t decide for itself whether it’s filled, state does, using the checked attribute.
- The trick is to compare each radio’s own value against the value in state.
- If they match, that radio is filled. If not, it’s empty.
import { useState } from "react";
function PlanPicker() { const [plan, setPlan] = useState("free");
return ( <input type="radio" name="plan" value="pro" checked={plan === "pro"} onChange={(e) => setPlan(e.target.value)} /> );}Let’s read that input line by line.
value="pro"is this radio’s own answer. It never changes.checked={plan === "pro"}asks a yes or no question: does state currently hold “pro”? If yes, this radio is filled. If no, it’s empty.onChange={(e) => setPlan(e.target.value)}runs when the user clicks this radio, and saves its value into state.
So state holds one string, and each radio just checks “is that string ME?”. Only the matching one shows as filled.
⌨️ Reading the chosen value
You already saw the key line, but it’s worth slowing down on because it’s the whole game.
When the user clicks a radio, React fires onChange. The value of the radio they clicked sits at e.target.value, and we push it into state.
function handleChange(e) { setPlan(e.target.value); // e.target.value is the clicked radio's value}Here’s what’s happening in that one line.
e.targetis the radio the user just clicked.e.target.valueis that radio’svalueattribute, like “pro” or “team”.setPlan(...)saves it, so now state knows the user’s choice.
Notice we don’t read which one is checked by hand. We just grab the value of whatever got clicked. Simple and clean.
⚠️ The common mistakes
Two mistakes trip people up here, and they both come from forgetting what makes the group work. Let’s see the broken version next to the working one.
// ❌ Wrong - no shared name, and no checked tied to state<input type="radio" value="free" /> Free<input type="radio" value="pro" /> Pro// Problem 1: different (missing) names, so they aren't one group.// The user can fill BOTH at once.// Problem 2: no checked and no onChange, so React doesn't control them.
// ✅ Right - same name across the group, checked bound to state<input type="radio" name="plan" value="free" checked={plan === "free"} onChange={(e) => setPlan(e.target.value)}/> Free<input type="radio" name="plan" value="pro" checked={plan === "pro"} onChange={(e) => setPlan(e.target.value)}/> ProHere’s why the right side works and the wrong side doesn’t.
- Without a shared
name, the radios aren’t a group at all, so the browser lets the user fill more than one. The sharedname="plan"fixes that. - Without
checkedandonChange, React isn’t in charge of the value. Bindingcheckedto state and saving the click withonChangemakes it a real controlled component.
Same name, or it's not a group
If your radios have different names, or no name, they stop acting as one group and the user can select several at once. Give every radio in the group the exact same name.
💡 Putting it together: a plan picker
Now let’s join it all into one small working example. Three radios for Free, Pro, and Team, all controlled, and a line that shows the current choice.
import { useState } from "react";
function PlanPicker() { const [plan, setPlan] = useState("free");
function handleChange(e) { setPlan(e.target.value); }
return ( <div> <label> <input type="radio" name="plan" value="free" checked={plan === "free"} onChange={handleChange} /> Free </label> <label> <input type="radio" name="plan" value="pro" checked={plan === "pro"} onChange={handleChange} /> Pro </label> <label> <input type="radio" name="plan" value="team" checked={plan === "team"} onChange={handleChange} /> Team </label>
<p>You chose: {plan}</p> </div> );}Here’s the full flow, step by step.
- State starts at “free”, so the Free radio is filled when the page loads.
- The user clicks Pro.
onChangefires, andhandleChangesaves “pro” into state. - React re-renders. Now
plan === "pro"is true, so Pro shows filled and the others go empty. - The
<p>readsplanstraight from state, so it updates to “You chose: pro” on its own.
Output
(page loads with Free selected)You chose: free
(user clicks Pro)You chose: proSo the whole idea lands here. One piece of state holds the answer, every radio compares itself to it, and the screen always matches state.
✅ Best practices
A few small habits keep your radio groups clean and bug-free.
- Give every radio in the group the same
name, and a uniquevalueeach. - Bind
checkedto a comparison likechecked={plan === "pro"}, so state controls which one is filled. - Read the choice from
e.target.valueinsideonChange, and save it with your setter. - Set a sensible initial value in
useState, so one option is selected when the page first loads. - Wrap each input in a
<label>so clicking the text also picks that radio.
Start with one option already selected
Passing a real value to useState, like useState("free"), means the page opens with one radio already chosen. That’s usually nicer than leaving the whole group empty.
🧩 What You’ve Learned
- ✅ A radio group lets the user pick ONE option, with all choices shown at once
- ✅ Every radio in the group shares the same
name, and each has its own uniquevalue - ✅ You control a radio with
checked={selected === thisValue}, so state decides which one is filled - ✅ You read the picked value from
e.target.valueinsideonChangeand save it with a setter - ✅ Missing a shared
namelets the user select more than one, which breaks the group - ✅ A sensible initial value in
useStatemeans one option is selected on first load
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Why must every radio in a group share the same name?
Why: The shared name is what groups the radios together. With the same name, selecting one automatically clears the others. Different names (or no name) let the user pick several at once.
- 2
How do you make a radio controlled by state?
Why: Each radio keeps its own fixed value, and you bind checked to a comparison against state. When state equals that radio's value, the radio is filled.
- 3
Inside onChange, how do you read which option the user picked?
Why: e.target is the radio the user clicked, so e.target.value is that radio's value attribute, like 'pro' or 'team'. You save it into state with your setter.
- 4
What happens if your radios have different names or no name at all?
Why: Without a shared name the radios aren't a group, so the browser lets the user fill several at once. Giving them all the same name fixes it.
🚀 What’s Next?
Now you can let the user pick one option from a group and keep that choice in state. Next we’ll handle the opposite case, where the user can turn options on and off independently, one true or false value at a time.