Previous Article
How to Check Which Checkboxes and Radio Buttons Are Selected in JavaScript
17 November 2018
Styling checkboxes and radio buttons used to require a plugin. iCheck, icheck-bootstrap and their contemporaries existed because browsers gave you almost no control over form control appearance.
That is no longer true. Modern CSS styles these natively, with better accessibility and no dependency. If you are still loading a library for this, you can delete it.
If all you want is your brand colour on otherwise native controls, this is the entire solution:
input[type="checkbox"],
input[type="radio"] {
accent-color: #2563EB;
width: 1.15em;
height: 1.15em;
}
Or set it once for the whole document:
:root { accent-color: #2563EB; }
Supported in every current browser. You keep the platform's native control — which means correct keyboard behaviour, correct high-contrast mode rendering, correct touch targets on mobile, and correct appearance in the user's dark mode — and you change the colour. For most projects this is where the work should stop.
The browser also picks a contrasting checkmark colour automatically, so a light accent gets a dark tick without you specifying anything.
When the design genuinely requires a custom shape, remove the native rendering and draw your own:
.check {
display: inline-flex;
align-items: center;
gap: 0.6rem;
cursor: pointer;
line-height: 1.4;
}
.check input {
appearance: none;
-webkit-appearance: none;
/* Keep the real input in the layout — do not display:none it. */
flex: none;
width: 1.25rem;
height: 1.25rem;
margin: 0;
border: 2px solid #94A3B8;
border-radius: 0.3rem;
background: #fff;
cursor: pointer;
display: grid;
place-content: center;
transition: background-color .15s, border-color .15s;
}
/* Radios differ only in being round. */
.check input[type="radio"] { border-radius: 50%; }
/* The tick, drawn with a pseudo-element and revealed on :checked. */
.check input::before {
content: "";
width: 0.7rem;
height: 0.7rem;
transform: scale(0);
transition: transform .12s ease-in-out;
box-shadow: inset 1em 1em #fff;
/* A checkmark polygon — no icon font, no SVG file. */
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
.check input[type="radio"]::before {
border-radius: 50%;
clip-path: none;
width: 0.6rem;
height: 0.6rem;
}
.check input:checked {
background: #2563EB;
border-color: #2563EB;
}
.check input:checked::before { transform: scale(1); }
/* Indeterminate — set from JavaScript, common for "select all" headers. */
.check input:indeterminate {
background: #2563EB;
border-color: #2563EB;
}
.check input:indeterminate::before {
transform: scale(1);
clip-path: polygon(15% 42%, 85% 42%, 85% 58%, 15% 58%);
}
/* Keyboard focus only — no ring on mouse click. */
.check input:focus-visible {
outline: 2px solid #2563EB;
outline-offset: 2px;
}
.check input:disabled {
border-color: #CBD5E1;
background: #F1F5F9;
cursor: not-allowed;
}
.check:has(input:disabled) { color: #94A3B8; cursor: not-allowed; }
:indeterminate can only be set from JavaScript, never from HTML.<label class="check"> <input type="checkbox" name="terms" /> <span>I accept the terms</span> </label> <label class="check"> <input type="radio" name="plan" value="monthly" /> <span>Monthly</span> </label> <label class="check"> <input type="radio" name="plan" value="annual" /> <span>Annual</span> </label>
Wrapping the input inside the <label> associates them implicitly, so no matching id and for attributes are required and they cannot drift out of sync. Either approach is valid; this one has fewer moving parts.
Group related radios in a fieldset so screen readers announce what the choice is about:
<fieldset> <legend>Billing period</legend> <!-- radios --> </fieldset>
This is where the old plugin-based approaches usually went wrong, and where hand-rolled CSS still does.
display: none or visibility: hidden the input. That removes it from the accessibility tree and from the tab order entirely, so keyboard users cannot reach it and screen readers do not announce it. Use appearance: none and keep the element in the layout — that is precisely what it is for.outline: none on its own makes a form unusable by keyboard. Use :focus-visible so the indicator shows for keyboard users without appearing on mouse clicks.<div>. A div with a click handler has no implicit role, no keyboard activation, no form participation, and will not submit with the form.@media (forced-colors: active) block that restores a visible outline.@media (forced-colors: active) {
.check input { border-color: CanvasText; }
.check input:checked { background: Highlight; }
}
Useful for a "select all" header when only some children are selected. It cannot be set in HTML — only through the DOM:
const all = document.querySelector('#select-all');
const items = document.querySelectorAll('.item-check');
function sync() {
const checked = [...items].filter((i) => i.checked).length;
all.checked = checked === items.length;
all.indeterminate = checked > 0 && checked < items.length;
}
all.addEventListener('change', () => {
items.forEach((i) => { i.checked = all.checked; });
});
items.forEach((i) => i.addEventListener('change', sync));
sync();
No. The original iCheck plugin depends on jQuery and has been unmaintained for years; icheck-bootstrap is CSS-only but ties you to a fixed set of colour classes and to Bootstrap's markup conventions.
Everything either of them offered is now a handful of CSS rules with no dependency, no jQuery, no extra HTTP request, and better accessibility — because you are styling the real control rather than hiding it and drawing a replacement.
If you only need brand colours, accent-color alone is a single line and keeps every native behaviour intact. Reach for appearance: none only when the design demands a shape the platform will not give you.
Advertisement
Written by
Amit VermaFounder / Senior Software Engineer
Amit leads engineering at Being Idea. With 15+ years building scalable software products across global markets, he drives architecture decisions and engineering culture across every engagement.
More articles by AmitYou might also like
We ship software that scales. Let's work together.