HTML radio buttons are <input type="radio"> elements that share the same name, so only one in the group can be selected. Each needs its own value, which is what gets submitted. Wrap the group in a <fieldset> with a <legend> for accessibility, add checked for a default or required to force an answer, and read the choice in JavaScript with form.elements.name.value.
This guide covers building radio groups correctly, yes/no questions, horizontal layouts, custom styling and reading the value with JavaScript. It's part of the HTML form elements reference.
A radio button form
<form action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST">
<fieldset>
<legend>Which plan are you interested in?</legend>
<label><input type="radio" name="plan" value="starter" checked> Starter</label>
<label><input type="radio" name="plan" value="pro"> Pro</label>
<label><input type="radio" name="plan" value="business"> Business</label>
</fieldset>
<label for="email">Email</label>
<input id="email" type="email" name="email" autocomplete="email" required>
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
<button type="submit">Send</button>
</form>If someone picks Pro, the form submits plan=pro. The label text ("Pro") is only for people. The value ("pro") is what you receive.
How radio groups work
- Same
name, one group. Radio buttons with the samenamein the same form form a group. Checking one unchecks the rest. A differentnamestarts a new, independent group. - Every radio needs a
value. Without one, the browser submitson, which tells you nothing about which option was picked. - Arrow keys move between options. The whole group is a single Tab stop, and the arrow keys change the selection. You get this for free with native radios.
- Once selected, a group can't be cleared by clicking again. If "no answer" is valid, add it as an explicit option.
Default selection with checked
Add checked to the option that should be selected when the page loads. Only one radio per group should have it. A default is helpful when one answer is clearly the most common, but avoid it for questions where a pre-selected answer would bias the response, such as a rating.
Required radio groups
Add required to any one radio in the group, and the browser won't submit the form until one of the options is checked:
<label><input type="radio" name="size" value="s" required> Small</label>
<label><input type="radio" name="size" value="m"> Medium</label>
<label><input type="radio" name="size" value="l"> Large</label>required has no effect when one option is already checked, because the group always has an answer.
Fieldset and legend for accessibility
Each radio has its own label ("Small"), but the question ("What size?") also needs to be read. Wrap the group in <fieldset> and put the question in <legend>. Screen readers then announce the legend when focus enters the group, for example "What size? Small, radio button, 1 of 3".
A heading or <p> above the radios isn't associated with them, so it isn't announced. The fieldset and legend guide covers styling the group so it doesn't look like a 1990s box.
Yes/no radio buttons
A yes/no question is a two-option radio group. Use clear values that read well in your inbox or spreadsheet:
<fieldset>
<legend>Have you used a form backend before?</legend>
<label><input type="radio" name="used_before" value="yes" required> Yes</label>
<label><input type="radio" name="used_before" value="no"> No</label>
</fieldset>Why not a single checkbox? An unchecked checkbox sends nothing, so you can't tell "no" from "didn't answer". A required yes/no radio group always sends an explicit answer. For simple agreement, such as accepting terms, a single checkbox is still the right choice.
Horizontal radio buttons
Radios inside labels are inline by default, but they wrap awkwardly and the spacing is inconsistent. A flex row gives you control:
<fieldset class="radio-row">
<legend>Preferred contact method</legend>
<label><input type="radio" name="contact_method" value="email" checked> Email</label>
<label><input type="radio" name="contact_method" value="phone"> Phone</label>
<label><input type="radio" name="contact_method" value="whatsapp"> WhatsApp</label>
</fieldset>
<style>
.radio-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.5rem;
border: 0;
padding: 0;
margin: 0;
min-width: 0;
}
.radio-row legend {
flex-basis: 100%;
padding: 0;
margin-bottom: 0.5rem;
}
.radio-row label {
display: inline-flex;
align-items: center;
gap: 0.375rem;
}
</style>flex-wrap: wrap lets the options stack on narrow screens instead of overflowing. For a vertical list, use flex-direction: column.
Custom radio button styling
accent-color
The simplest change is accent-color, which recolours the native control while keeping its behaviour and accessibility:
input[type="radio"] {
accent-color: #4f46e5;
width: 1.125rem;
height: 1.125rem;
}A fully custom radio with appearance: none
For full control, remove the native look with appearance: none and draw it yourself. The input stays a real radio, so keyboard support, required and form submission still work:
input[type="radio"] {
appearance: none;
width: 1.125rem;
height: 1.125rem;
margin: 0;
border: 2px solid #6b7280;
border-radius: 50%;
display: inline-grid;
place-content: center;
}
input[type="radio"]::before {
content: "";
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background: #4f46e5;
transform: scale(0);
transition: transform 120ms ease;
}
input[type="radio"]:checked {
border-color: #4f46e5;
}
input[type="radio"]:checked::before {
transform: scale(1);
}
input[type="radio"]:focus-visible {
outline: 2px solid #4f46e5;
outline-offset: 2px;
}Avoid hiding the input with display: none and styling a <span> instead. That removes it from keyboard navigation and from the accessibility tree.
Get the selected radio value in JavaScript
There are three common ways. All assume the form above.
const form = document.querySelector("form");
// 1. RadioNodeList: the simplest. Returns "" if nothing is checked.
const plan = form.elements.plan.value;
// 2. The :checked selector. Returns null if nothing is checked.
const checked = form.querySelector('input[name="plan"]:checked');
const plan2 = checked ? checked.value : null;
// 3. FormData: what would actually be submitted. Returns null if nothing is checked.
const plan3 = new FormData(form).get("plan");To react when the selection changes, listen for change on the form, since the event bubbles:
form.addEventListener("change", (event) => {
if (event.target.name === "plan") {
console.log("Selected plan:", event.target.value);
}
});You can also set the selection: form.elements.plan.value = "pro" checks the radio whose value is pro.
What is submitted when none is selected
If no radio in a group is checked, the group's name is left out of the submission completely. It isn't sent as an empty string. On the receiving end, the field simply doesn't exist. If you need to know that someone skipped the question, make the group required, or add an explicit option such as "Prefer not to say".
Radio vs checkbox vs select
| Use | When |
|---|---|
| Radio buttons | One answer from 2 to about 6 options, where seeing every option helps. |
| Checkboxes | Any number of answers, including none, or a single on/off choice. |
| Select dropdown | One answer from a long list, such as a country, or when space is tight. |
Sending radio button answers
A radio group's answer arrives as an ordinary field, such as plan: pro, alongside the rest of the form. Point the form's action at https://formsubmit.app/f/YOUR_FORM_ID with method="POST" and each submission is stored in your dashboard and emailed to you, with spam filtered out. See how to send an HTML form to email for the full setup, or go back to the HTML form elements overview.