Sitemap

How to Style Custom Checkboxes with CSS (Without Breaking Accessibility)

2 min readOct 12, 2026

--

Press enter or click to view image in full size

Default checkboxes are ugly and inconsistent across browsers. Chrome renders them one way. Firefox renders them another. Safari does its own thing entirely.

Styling them properly is harder than it should be. You hide the native input. You build a fake one. You forget about keyboard focus. You test with a screen reader and realize it’s broken.

Let’s break down how to do it right.

The HTML Structure

The key is to keep the native <input> in the DOM for accessibility, but visually replace it with a styled element.

html

php-template<label class="custom-checkbox">
<input type="checkbox" />
<span class="checkmark"></span>
Accept terms
</label>

The <input> stays accessible to screen readers and keyboards. The <span> becomes the visual checkbox.

The CSS

css

css.custom-checkbox {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.custom-checkbox input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.checkmark {
width: 20px;
height: 20px;
border: 2px solid #ccc;
border-radius: 4px;
background: white;
transition: all 0.2s;
}
.custom-checkbox input:checked + .checkmark {
background: #4f46e5;
border-color: #4f46e5;
}
.custom-checkbox input:focus + .checkmark {
outline: 2px solid #4f46e5;
outline-offset: 2px;
}

Adding a Checkmark Icon

Use a pseudo-element on the checkmark:

css

css.checkmark::after {
content: "";
display: none;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
margin-left: 6px;
margin-top: 2px;
}
.custom-checkbox input:checked + .checkmark::after {
display: block;
}

For Toggle Switches

Same technique, different shape:

Get KandZ- Tuts’s stories in your inbox

Join Medium for free to get updates from this writer.

css

css.toggle {
width: 44px;
height: 24px;
border-radius: 12px;
background: #ccc;
position: relative;
}
.toggle::after {
content: "";
width: 20px;
height: 20px;
border-radius: 50%;
background: white;
position: absolute;
top: 2px;
left: 2px;
transition: transform 0.2s;
}
input:checked + .toggle {
background: #4f46e5;
}
input:checked + .toggle::after {
transform: translateX(20px);
}

Accessibility Considerations

  • Never remove the input from the DOM — use opacity: 0 and position: absolute
  • Always include :focus styles so keyboard users can see the state
  • Use cursor: pointer on the label
  • Keep the label clickable so the whole area toggles the checkbox
  • Test with a screen reader — the input should still announce correctly

Common Gotchas

  • The + sibling selector only works if the styled element comes after the input
  • appearance: none removes native styling but keep the input accessible
  • Dark mode requires separate color variables
  • Some browsers render :focus-visible differently than :focus

Prototyping Visually

If you want to design accessible custom checkboxes and toggles without writing all this CSS by hand, I built a free CSS Custom Checkbox Generator. It has live interactive previews and exports clean, accessible code.

Try it here

100% client-side. Nothing uploaded.

How do you handle custom form controls in your projects? Do you use native inputs or build your own? Let me know in the comments.

--

--