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.target is the radio the user just clicked.
  • e.target.value is that radio’s value attribute, 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)}
/> Pro

Here’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 shared name="plan" fixes that.
  • Without checked and onChange, React isn’t in charge of the value. Binding checked to state and saving the click with onChange makes 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. onChange fires, and handleChange saves “pro” into state.
  • React re-renders. Now plan === "pro" is true, so Pro shows filled and the others go empty.
  • The <p> reads plan straight 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: pro

So 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 unique value each.
  • Bind checked to a comparison like checked={plan === "pro"}, so state controls which one is filled.
  • Read the choice from e.target.value inside onChange, 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 unique value
  • ✅ You control a radio with checked={selected === thisValue}, so state decides which one is filled
  • ✅ You read the picked value from e.target.value inside onChange and save it with a setter
  • ✅ Missing a shared name lets the user select more than one, which breaks the group
  • ✅ A sensible initial value in useState means one option is selected on first load

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 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. 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. 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. 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.

React Checkboxes

Share & Connect