Radio Buttons in JavaScript: A Complete Guide

Aug 17, 2026·18 min read

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, and FormData.

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.

Document tree A · Form AOne radio groupname=contactEmailPhoneTextname=contactDifferent form or tree → different group
A shared name creates one group only inside the same form and tree.

Within one group, checking an option unchecks the previously selected option. Use checkboxes instead when several independent choices may remain checked at once.

One choice versus independent choicesRadio group:beforeEmailPhoneclickRadio group:afterEmailPhoneCheckboxes:beforeEmailPhoneclickCheckboxes:afterEmailPhone
Radio selection moves; checkbox selections accumulate.

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:

Three views of one selectionCheckedradioPhonename=contactquerySelectorselected <input>RadioNodeListvalue: phoneFormDatacontact → phone
One checked radio can be viewed as an element, a group value, or submitted data.
APINo selectionUse it when
querySelector(':checked')null elementYou 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')nullYou 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:

  • checked reports and changes the current selection.
  • The HTML checked attribute establishes the default selection.
  • defaultChecked reflects 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:

Default and current are separateHTML defaultEmailchecked = trueCurrent statePhonereset()Reset copies the default into current state
Changing the current choice leaves the HTML default available for reset.
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.

Events rise to a stable listenerformchange listenerchecks event.targetEmailPhoneVideo calladded later
One form listener receives changes from present and future radios.

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.

Required belongs to the groupcontact grouprequiredEmailattribute is herePhoneTextPhone makes the group valid
Any enabled choice can satisfy a required radio group.

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:

What crosses into FormData?Checked +enabledvalue=emailUnchecked ordisabledChecked +enabledno value attributeSUBMITemail entryno entry’on’ entry
Only eligible checked radios become FormData entries.
  • A checked radio contributes its name and value.
  • An unchecked group contributes no entry.
  • A checked radio without a value attribute 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:

SymptomCauseFix
Several choices remain selectedThe radios have different name values, different form owners, or belong to different treesPut 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 attributeGive every option a unique value
Reading .value throwsquerySelector(':checked') returned nullUse selected?.value ?? null or test selected first
Code reports a value from an unchecked radioIt read an input’s .value without checking .checkedSelect with :checked or read RadioNodeList.value
Reset chooses an unexpected optionMore than one radio was marked checked in the HTMLDeclare one default at most
form.reset is not a functionA control named or identified as reset masks the form methodUse form.elements['reset'] for the control and avoid that name when practical
A new radio never triggers the handlerListeners were attached only to radios present during setupDelegate 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.

Value is a label; checked is a gateUncheckedPhonevalue=‘phone’contributes nothingselectCheckedPhonevalue=‘phone’contributes phone
A radio keeps its value even while its checked state is off.

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?
Use 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?
Set the radio input's 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?
An unchecked radio group contributes no entry to submitted form data, so new FormData(form).get(name) returns null. Give the group a checked default or mark one member required when a selection is mandatory.
What is the difference between radio buttons and checkboxes?
Radio buttons represent one choice from a group, while checkboxes represent independent on or off choices. Checking one radio unchecks the other members of its group; checking one checkbox does not change another checkbox.