An HTML checkbox is <input type="checkbox" name="..." value="...">, and it is submitted only when ticked. Use one on its own for consent, or several with the same name to let people choose multiple options. An unchecked box sends nothing, and a box without a value sends on. Add required to force a tick, and style it with accent-color.
This guide covers single and multiple checkboxes, what actually gets submitted, required and indeterminate states, styling, and reading checkboxes in JavaScript. It's part of the HTML form elements reference.
A checkbox form
<form action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST">
<label for="email">Email</label>
<input id="email" type="email" name="email" autocomplete="email" required>
<fieldset>
<legend>Topics you'd like to hear about</legend>
<label><input type="checkbox" name="topics" value="product-updates"> Product updates</label>
<label><input type="checkbox" name="topics" value="tutorials"> Tutorials</label>
<label><input type="checkbox" name="topics" value="events"> Events</label>
</fieldset>
<label>
<input type="checkbox" name="consent" value="yes" required>
I agree to receive emails and can unsubscribe at any time
</label>
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
<button type="submit">Subscribe</button>
</form>Wrapping each checkbox in its <label> makes the text clickable, which turns a tiny target into a comfortable one. You can also use for and id if the label needs to sit elsewhere.
A single checkbox for consent
A single checkbox is a yes/no switch that defaults to "no". It's the right tool for agreeing to terms, opting in to marketing or confirming something:
<label>
<input type="checkbox" name="consent" value="yes" required>
I agree to the <a href="/privacy">privacy policy</a>
</label>Two points for consent boxes:
- Don't pre-tick marketing opt-ins. Under GDPR, a pre-ticked box isn't valid consent. Leave
checkedoff. More in GDPR-compliant contact forms. - Give it a meaningful
value.consent=yesin your inbox is clearer thanconsent=on.
Multiple checkboxes with the same name
To let people pick several options, give every checkbox in the group the same name and a different value. If someone ticks "Tutorials" and "Events" in the form above, the browser sends:
topics=tutorials&topics=eventsThe same key appears once for each ticked box. FormSubmit keeps multiple values for the same name, so topics arrives as a list containing both values rather than only the last one.
What about name="topics[]"?
The [] suffix is a PHP convention. PHP only keeps the last value of a repeated key unless the name ends in []. Many tutorials use it for that reason. FormSubmit accepts either form: plain repeated names and names ending in [] both keep every value. If you're not posting to PHP, the plain name is cleaner.
Unchecked checkboxes aren't submitted
An unchecked checkbox is left out of the submission completely. There's no consent=off or consent= with an empty value. On the receiving end, the field simply doesn't exist.
That's usually fine, but if you need an explicit "no", there are two options:
- Use a yes/no radio group instead. See HTML radio buttons.
- Add a hidden input with the same name before the checkbox:
<input type="hidden" name="newsletter" value="no">
<label><input type="checkbox" name="newsletter" value="yes"> Send me the newsletter</label>When ticked, both values are sent (no then yes), and when not ticked only no is sent. This trick is common with servers that keep the last value. With FormSubmit, both values are kept, so the radio group is the clearer choice.
The default value is "on"
If a checkbox has no value attribute, a ticked box submits the string on:
<input type="checkbox" name="urgent"> <!-- submits urgent=on -->This works but is easy to misread in a spreadsheet or email. Set value to something descriptive.
Required checkboxes
required on a checkbox means that exact box must be ticked before the form submits. It's ideal for consent.
HTML has no "at least one in this group" rule. Putting required on every box in a group would force all of them to be ticked. To require at least one, check the group in JavaScript:
<script>
const form = document.querySelector("form");
const boxes = form.querySelectorAll('input[name="topics"]');
function validateTopics() {
const anyChecked = [...boxes].some((box) => box.checked);
boxes[0].setCustomValidity(anyChecked ? "" : "Choose at least one topic.");
}
boxes.forEach((box) => box.addEventListener("change", validateTopics));
validateTopics();
</script>setCustomValidity hooks into the browser's own validation, so the message appears on submit just like a built-in required error.
The indeterminate state
A checkbox can look "partly ticked", shown as a dash. This is useful for a "Select all" box when only some children are ticked. It can only be set with JavaScript:
<fieldset>
<legend>Services</legend>
<label><input type="checkbox" id="all-services"> Select all</label>
<label><input type="checkbox" name="services" value="design"> Design</label>
<label><input type="checkbox" name="services" value="development"> Development</label>
<label><input type="checkbox" name="services" value="hosting"> Hosting</label>
</fieldset>
<script>
const all = document.getElementById("all-services");
const items = document.querySelectorAll('input[name="services"]');
all.addEventListener("change", () => {
items.forEach((item) => { item.checked = all.checked; });
});
items.forEach((item) => item.addEventListener("change", () => {
const ticked = [...items].filter((i) => i.checked).length;
all.checked = ticked === items.length;
all.indeterminate = ticked > 0 && ticked < items.length;
}));
</script>indeterminate is purely visual. It isn't submitted, and what's sent still depends on checked. The "Select all" box has no name, so it's never submitted itself.
Styling checkboxes
accent-color
accent-color changes the colour of the native checkbox while keeping its accessibility and behaviour:
input[type="checkbox"] {
accent-color: #4f46e5;
width: 1.125rem;
height: 1.125rem;
}A custom checkbox
For a fully custom look, use appearance: none on the real input and draw the tick with a pseudo-element. The input stays focusable, keyboard-operable and submittable:
input[type="checkbox"] {
appearance: none;
width: 1.125rem;
height: 1.125rem;
margin: 0;
border: 2px solid #6b7280;
border-radius: 0.25rem;
display: inline-grid;
place-content: center;
}
input[type="checkbox"]::before {
content: "";
width: 0.625rem;
height: 0.625rem;
background: #fff;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0, 43% 62%);
transform: scale(0);
}
input[type="checkbox"]:checked {
background: #4f46e5;
border-color: #4f46e5;
}
input[type="checkbox"]:checked::before {
transform: scale(1);
}
input[type="checkbox"]:focus-visible {
outline: 2px solid #4f46e5;
outline-offset: 2px;
}Don't hide the input with display: none and style a sibling element instead. That breaks keyboard access.
Reading checkboxes in JavaScript
const form = document.querySelector("form");
// A single checkbox: true or false
const agreed = form.elements.consent.checked;
// All ticked values in a group, as an array
const topics = new FormData(form).getAll("topics");
// e.g. ["tutorials", "events"]
// The same with a selector
const topics2 = [...form.querySelectorAll('input[name="topics"]:checked')]
.map((box) => box.value);Use .checked, not .value, to know if a box is ticked. .value returns the value attribute whether the box is ticked or not.
Sending checkbox answers
Checkbox answers arrive as normal fields, with a group's ticked values kept together under one name. Point the form at https://formsubmit.app/f/YOUR_FORM_ID with method="POST" and each submission is stored, spam-filtered and emailed to you. The full setup is in how to send an HTML form to email. For other ways to collect choices, see radio buttons, the select dropdown or the HTML form elements overview.