CSS Checkbox Generator
Customised native checkbox styling with accessibility preserved.
<label class="custom-checkbox">
<input type="checkbox" checked />
<span class="box"></span>
<span>Subscribe to updates</span>
</label>
<style>
.custom-checkbox {
display: inline-flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.custom-checkbox input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.custom-checkbox .box {
position: relative;
display: inline-block;
width: 24px;
height: 24px;
background: #FFFFFF;
border: 1px solid #E3E8EE;
border-radius: 6px;
transition: background 150ms, border-color 150ms;
}
.custom-checkbox input:checked + .box {
background: #635BFF;
border-color: #635BFF;
}
.custom-checkbox input:focus-visible + .box {
box-shadow: 0 0 0 3px #635BFF33;
}
.custom-checkbox input:checked + .box::after {
content: "";
position: absolute;
left: 7px;
top: 4px;
width: 5px;
height: 11px;
border: solid #FFFFFF;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
</style>What is a CSS Checkbox Generator?
A CSS checkbox generator produces styled, brand-matched checkboxes without sacrificing the native <input type="checkbox"> element's behaviour. The trick is to hide the native input visually (not with display: none, which would break keyboard access) while keeping it in the layout, then style a sibling <span> to look like a custom checkbox. When the user clicks the label, the browser toggles the native input and CSS sibling selectors (:checked + .box) restyle the visible box.
Keep the native input alive. Replacing it with a div destroys form submission, keyboard focus (Tab / Space), screen-reader announcements, the :checked pseudo-class, and the browser's built-in indeterminate state. The generator's pattern hides the input with position: absolute; opacity: 0 so it remains focusable and submittable, then draws the visual box as a pure-CSS sibling.
Focus visibility. The generator adds a focus ring via :focus-visible (not :focus), which shows the ring for keyboard users but not for mouse clicks — the modern accessibility best practice. Users who tab in can see exactly where keyboard focus is; users who click don't get distracting rings.
Check-mark options. Tick uses two CSS borders rotated to form a checkmark — the most recognisable. Dot is a filled circle, appropriate for single-selection toggles that technically use a checkbox ("Remember me" is an example). Cross is a rotated-lines X, occasionally used in deletion/removal UIs. Pick based on semantic expectation: tick for "completed", dot for "active", cross for "excluded".
Sizing. The default 24px is comfortable for desktop UI. Mobile targets should be at least 44×44px of clickable area (the wrapper <label>provides this even when the visual box is smaller). Don't go below 20px on any device — small checkboxes are disproportionately error-prone.
Animation. The generator transitions background and border colour over 150ms, which feels responsive without being slow. For a springier feel, add atransition on transform and scale(1.1) briefly on check. For a more muted feel, drop the transition entirely.
How to generate a styled CSS checkbox
- Pick a shape (square, rounded, circle) and check-mark style.
- Set size and colours to match your design system.
- Copy the HTML + CSS and paste into your component library.
- Wrap with a label and matching text — the whole label becomes clickable.
Features
- Native input preserved for keyboard, focus, form submission.
- Three shapes and three check-mark styles.
- Per-state colour control: checked, unchecked, border, mark.
- Focus-visible ring for accessibility.
- Copy-ready HTML + CSS snippet.
Frequently asked questions
- Why hide the native checkbox?
- The native input is ugly to style directly across browsers. Hiding it (with opacity: 0, not display: none) preserves keyboard access, form submission, and screen-reader behaviour while letting us style a sibling element to look custom.
- Does this work with screen readers?
- Yes. The native input is still in the DOM and focusable — screen readers announce it as a checkbox. Just make sure the <label> wrapper has descriptive text next to the visual box.
- How do I handle indeterminate state?
- Native checkboxes support indeterminate via JavaScript (input.indeterminate = true). You can add a CSS rule on :indeterminate to show a dash or half-fill on the visual box.
- Can I use this in forms?
- Yes — the native input still participates in form submission because it's present in the DOM, just visually hidden.