Radio Buttons in JavaScript: A Complete Guide
Articles / Radio Buttons in JavaScript: A Complete Guide
Radio Buttons in JavaScript: A Complete Guide
Aug 17, 2026 · 15 min read
The shortest null-safe answer is document.querySelector('input[name="contact"]:checked')?.value ?? null: it returns the selected radio value, or null when nothing is selected.
A radio button group lets you choose one value from several options, and JavaScript can read, select, validate, reset, and submit that choice through DOM selectors,
RadioNodeList, andFormData.
Build a correct radio button group
A radio button is an <input> whose type is "radio". A radio group contains radio inputs with the same non-empty name, the same form owner, and the same tree.
That full boundary matters. Matching names alone do not combine radios belonging to different forms or different document trees.
Within one group, checking an option unchecks the previously selected option. Use checkboxes instead when several independent choices may remain checked at once.
The canonical example in this article asks how a customer wants to be contacted:
<form id="contact-form">
<fieldset id="contact-options">
<legend>How should we contact you?</legend>
<label for="contact-email">
<input
id="contact-email"
type="radio"
name="contact"
value="email"
required
checked
>
Email
</label>
<label for="contact-phone">
<input
id="contact-phone"
type="radio"
name="contact"
value="phone"
>
Phone
</label>
<label for="contact-text">
<input
id="contact-text"
type="radio"
name="contact"
value="text"
>
Text message
</label>
<label for="contact-pager">
<input
id="contact-pager"
type="radio"
name="contact"
value="pager"
disabled
>
Pager, unavailable
</label>
</fieldset>
<p id="contact-status" aria-live="polite"></p>
<button id="select-phone" type="button">Select phone</button>
<button id="add-video" type="button">Add video call</button>
<button id="restore-contact" type="button">Restore default</button>
<button type="submit">Save preference</button>
</form>
Every radio has the shared name contact and a unique submitted value. Its unique id connects it to a label, while fieldset and legend identify what the entire set means.
The checked attribute makes email the initial selection. Remove that attribute if the form must start without an answer. The required attribute then makes the browser ask for one before submission.
HTML carries the meaning and keyboard behaviour. JavaScript reads or changes the resulting state. The same split appears throughout the browser environment and keeps the control usable when scripts fail.
Get the selected radio button and value
There are three useful ways to read the contact group. The right one depends on whether you need the selected element, a convenient group value, or the data the form would submit.
querySelector(':checked')
Use :checked when you need the selected input itself:
const selected = document.querySelector(
'input[name="contact"]:checked'
);
const contact = selected?.value ?? null;
document.querySelector() returns the first matching element, or null when no element matches. Optional chaining stops the property read when selected is null, and ?? null gives the missing case one explicit result.
You can scope the search to the form when the page contains several forms:
const form = document.querySelector('#contact-form');
const selected = form.querySelector(
'input[name="contact"]:checked'
);
const contact = selected?.value ?? null;
Keep :checked in the selector. Reading document.querySelector('input[name="contact"]').value finds the first radio’s assigned value whether that radio is selected or not.
form.elements[name].value
The form’s elements collection contains its associated controls and supports lookup by name or id. Several same-named radios produce a RadioNodeList, whose value represents the checked member:
const form = document.querySelector('#contact-form');
const contactGroup = form.elements['contact'];
const contact = contactGroup.value;
When nothing is checked, contactGroup.value is an empty string. This is compact when you already have the form and need only the group’s value.
Bracketed form.elements['contact'] access is the clearest general pattern. A form control named reset, action, or method can collide with properties already exposed by the form object if you try to read the control directly from form.
FormData.get()
Use FormData when you want the current submitted values:
const form = document.querySelector('#contact-form');
const data = new FormData(form);
const contact = data.get('contact');
If no contact radio is selected, the group contributes no entry and get('contact') returns null. Disabled controls are excluded too, so the disabled pager option does not appear.
The three APIs answer related but different questions:
| API | No selection | Use it when |
|---|---|---|
querySelector(':checked') | null element | You need the selected input, its value, or another property |
form.elements['contact'].value | "" | You already have the form and need one group value |
new FormData(form).get('contact') | null | You need the value as it participates in form submission |
That difference between an element and its property is part of the wider DOM model covered in Node properties: type, tag and contents.
Check, select, and reset radio buttons
The checked property is a boolean describing the radio’s current state. Test it when you already have a particular input:
const email = document.querySelector('#contact-email');
if (email.checked) {
console.log('email is selected');
}
With the canonical HTML, this prints:
email is selected
Set the property to choose an option:
const phone = document.querySelector('#contact-phone');
phone.checked = true;
Phone becomes checked, and email becomes unchecked because both inputs belong to the same group.
A RadioNodeList can select by value instead. Assignment checks the first group member whose value matches:
const form = document.querySelector('#contact-form');
form.elements['contact'].value = 'text';
The string must match the HTML value, not the visible label. Assigning "Text message" does not match value="text".
Three pieces of state have similar names:
checkedreports and changes the current selection.- The HTML
checkedattribute establishes the default selection. defaultCheckedreflects whether that default attribute is present.
Changing input.checked changes what is selected now without replacing the default. Calling form.reset() restores the controls to their default values, so the canonical form returns to email:
const form = document.querySelector('#contact-form');
form.elements['contact'].value = 'phone';
form.reset();
console.log(form.elements['contact'].value);
email
If code needs other page content to follow a programmatic selection, call that update logic deliberately. Do not depend on a user interaction handler to run as a side effect of assigning checked or RadioNodeList.value.
The styles and classes guide shows how CSS can target the resulting :checked state without copying it into another class.
Respond to radio button changes
When a user selects a radio, that radio fires change as it becomes checked; programmatic assignments do not fire the event. The group member that becomes unchecked does not fire a second change event.
For a fixed group, you could attach one listener to every input. A form-level listener is more useful because it also covers matching radios inserted later:
form.addEventListener('change', (event) => {
if (!event.target.matches('input[type="radio"][name="contact"]')) {
return;
}
contactStatus.textContent =
`Selected contact method: ${event.target.value}`;
});
The listener sits on the stable form. When a radio changes, the event reaches the form, and event.target remains the radio that changed. This pattern is event delegation, which Introduction to browser events develops from the underlying event flow.
The canonical implementation uses that same listener while adding a video-call option after page load:
const form = document.querySelector('#contact-form');
const contactOptions = document.querySelector('#contact-options');
const contactStatus = document.querySelector('#contact-status');
const selectPhoneButton = document.querySelector('#select-phone');
const addVideoButton = document.querySelector('#add-video');
const restoreButton = document.querySelector('#restore-contact');
function updateContactStatus() {
const selected = form.querySelector(
'input[name="contact"]:checked'
);
contactStatus.textContent = selected
? `Selected contact method: ${selected.value}`
: 'No contact method selected';
}
form.addEventListener('change', (event) => {
if (!event.target.matches('input[type="radio"][name="contact"]')) {
return;
}
updateContactStatus();
});
selectPhoneButton.addEventListener('click', () => {
form.elements['contact'].value = 'phone';
updateContactStatus();
});
addVideoButton.addEventListener('click', () => {
if (document.querySelector('#contact-video')) {
return;
}
const label = document.createElement('label');
const radio = document.createElement('input');
radio.id = 'contact-video';
radio.type = 'radio';
radio.name = 'contact';
radio.value = 'video';
label.htmlFor = 'contact-video';
label.append(radio, ' Video call');
contactOptions.append(label);
});
restoreButton.addEventListener('click', () => {
form.reset();
updateContactStatus();
});
form.addEventListener('submit', (event) => {
event.preventDefault();
const data = new FormData(form);
const contact = data.get('contact');
contactStatus.textContent =
`Saved contact method: ${contact}`;
});
updateContactStatus();
This is the final JavaScript for the earlier HTML. The set button assigns through RadioNodeList.value, the reset button restores the HTML default, and both call the shared rendering function. The delegated listener handles email, phone, text, and the radio created later.
Validate and submit the selection
The required attribute applies to the group. If any member has it, the group is missing a required value when every member is unchecked.
In the canonical form, required appears on the email input, but email itself is not mandatory. Phone, text, or a dynamically added video option also satisfies the group requirement.
The browser performs its native validation before delivering a valid submission to the form’s submit listener. The final handler then prevents navigation for the demo and reads the current selection:
form.addEventListener('submit', (event) => {
event.preventDefault();
const data = new FormData(form);
const contact = data.get('contact');
contactStatus.textContent =
`Saved contact method: ${contact}`;
});
FormData uses the current names and submitted values of eligible form controls. Four radio-specific edges matter:
- A checked radio contributes its
nameandvalue. - An unchecked group contributes no entry.
- A checked radio without a
valueattribute contributes the default value"on". - A disabled radio is excluded. Descendants of a disabled fieldset are also excluded, except controls inside that fieldset’s first legend.
"on" rarely identifies a real choice. Give every radio an explicit value such as "email" or "phone".
The browser controls what reaches this handler through the page. It does not establish that later requests came from this form or kept its constraints.
A larger form may contain text inputs, checkboxes, and radio groups together. FormData reads their submission values through one interface, while radio-group lookups remain useful when the page must react before submission. JavaScript Fundamentals connects these browser APIs to the language features used to process the resulting data.
Common radio button bugs
Radio failures tend to come from one attribute or one assumption. Start with the visible symptom:
| Symptom | Cause | Fix |
|---|---|---|
| Several choices remain selected | The radios have different name values, different form owners, or belong to different trees | Put the intended group in one form and give every member the same non-empty name |
Submitted data contains "on" | The checked radio has no value attribute | Give every option a unique value |
Reading .value throws | querySelector(':checked') returned null | Use selected?.value ?? null or test selected first |
| Code reports a value from an unchecked radio | It read an input’s .value without checking .checked | Select with :checked or read RadioNodeList.value |
| Reset chooses an unexpected option | More than one radio was marked checked in the HTML | Declare one default at most |
form.reset is not a function | A control named or identified as reset masks the form method | Use form.elements['reset'] for the control and avoid that name when practical |
| A new radio never triggers the handler | Listeners were attached only to radios present during setup | Delegate change from the stable form |
The distinction between .value and .checked is the one to keep straight. A radio can have value="phone" while remaining unchecked; value says what it contributes if selected, and checked says whether it is selected now.
Done? Check the group boundary too. Two radios named contact do not form one group when their form owners or trees differ, even though a selector can find both.
Frequently asked questions
How do I get the selected radio button value in JavaScript?
document.querySelector('input[name="contact"]:checked')?.value ?? null when you need the selected element or its value. The expression returns null when no option is selected instead of trying to read .value from a missing element.How do I select a radio button with JavaScript?
checked property to true, or assign a matching value to the group's RadioNodeList.value. Selecting one radio automatically unchecks the other radios in the same group.Why does an unchecked radio group return null in FormData?
new FormData(form).get(name) returns null. Give the group a checked default or mark one member required when a selection is mandatory.