CSS Checkbox & Toggle Generator
Generate custom CSS checkboxes, toggle switches, and radio buttons — interactive preview, copy HTML + CSS, no JavaScript required
Medium (20px)
Live Preview (click to toggle)
HTML
<label class="toggle-label" for="custom-input">
<input type="checkbox" id="custom-input" class="toggle-input" />
<span class="toggle-slider"></span>
<span>Toggle label</span>
</label>CSS
.toggle-label {
display: inline-flex;
align-items: center;
gap: 10px;
cursor: pointer;
user-select: none;
}
.toggle-input {
opacity: 0;
position: absolute;
width: 0;
height: 0;
}
.toggle-slider {
position: relative;
display: inline-block;
width: 42px;
height: 20px;
background: #D1D5DB;
border-radius: 20px;
transition: background 0.3s ease;
flex-shrink: 0;
cursor: pointer;
}
.toggle-slider::before {
content: "";
position: absolute;
width: 15px;
height: 15px;
left: 3px;
top: 3px;
background: #ffffff;
border-radius: 50%;
transition: transform 0.3s ease;
}
.toggle-input:checked + .toggle-slider {
background: #3B82F6;
}
.toggle-input:checked + .toggle-slider::before {
transform: translateX(21px);
}
.toggle-input:focus-visible + .toggle-slider {
outline: 2px solid #3B82F6;
outline-offset: 2px;
}No JavaScript required. All animations use CSS transitions. The hidden native input preserves full accessibility — keyboard navigation, screen readers, and form submission work out of the box.
Browser-default checkboxes and radio buttons look different on every OS. Styling them with CSS used to require complex hacks involving appearance: none, pseudo-element trickery, and carefully hand-tuned pixel values. This tool generates complete, accessible custom checkbox, toggle switch, radio button, and sliding toggle CSS — no JavaScript required. Click the live preview to see the animation, then copy the HTML and CSS directly into your project.
Why Custom CSS Inputs (Without JavaScript)
Modern CSS makes it possible to create fully custom checkbox and toggle styles using only HTML and CSS — no JavaScript event listeners, no hidden state management, no React state. The core technique:
- Hide the native input with
opacity: 0(notdisplay: none— the input must remain in the DOM for accessibility and form behavior) - Style a sibling pseudo-element (via
::before/::afteror a<span>) using the:checkedCSS pseudo-class - Animate transitions with CSS
transitionproperty
The result is accessible (works with keyboard, screen readers, form submission), progressively enhanced, and fully themeable.
Style Types
Custom Checkbox
A styled square or rounded checkbox with a custom check mark. The check mark is drawn using a rotated CSS border trick — no icon font or SVG required:
.custom-checkbox-control::after {
content: "";
position: absolute;
width: 6px;
height: 11px;
border: 2px solid white;
border-top: none;
border-left: none;
transform: rotate(45deg);
}
Toggle Switch
The classic pill-shaped toggle with a circular thumb. Background transitions from the inactive color to the active color; the thumb slides across using translateX:
.toggle-input:checked + .toggle-slider {
background: #3B82F6;
}
.toggle-input:checked + .toggle-slider::before {
transform: translateX(24px);
}
Radio Button
A circular radio button with a dot indicator. The dot scales from 0 to 1 using transform: scale() for a smooth animation without layout shifts.
Sliding Toggle
A rectangular toggle with configurable border radius — anywhere from sharp square to pill shape. Useful for dashboard settings panels where the UI language is more geometric.
Core CSS Pattern
All styles follow the same accessible pattern:
<label class="custom-checkbox-label" for="my-checkbox">
<input type="checkbox" id="my-checkbox" class="custom-checkbox-input" />
<span class="custom-checkbox-control"></span>
<span>Checkbox label</span>
</label>
/* 1. Hide native input — keep in DOM for accessibility */
.custom-checkbox-input {
opacity: 0;
position: absolute;
width: 0;
height: 0;
}
/* 2. Style the visible control */
.custom-checkbox-control {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
border-radius: 4px;
border: 2px solid #D1D5DB;
background: #fff;
transition: background 0.3s ease, border-color 0.3s ease;
}
/* 3. Change style when checked */
.custom-checkbox-input:checked + .custom-checkbox-control {
background: #3B82F6;
border-color: #3B82F6;
}
Toggle Switch Complete Example
<label class="toggle-label" for="notifications">
<input type="checkbox" id="notifications" class="toggle-input" />
<span class="toggle-slider"></span>
<span>Enable notifications</span>
</label>
.toggle-label {
display: inline-flex;
align-items: center;
gap: 10px;
cursor: pointer;
user-select: none;
}
.toggle-input {
opacity: 0;
position: absolute;
width: 0;
height: 0;
}
.toggle-slider {
position: relative;
display: inline-block;
width: 48px;
height: 24px;
background: #D1D5DB;
border-radius: 24px;
transition: background 0.3s ease;
cursor: pointer;
}
.toggle-slider::before {
content: "";
position: absolute;
width: 18px;
height: 18px;
left: 3px;
top: 3px;
background: white;
border-radius: 50%;
transition: transform 0.3s ease;
}
.toggle-input:checked + .toggle-slider {
background: #3B82F6;
}
.toggle-input:checked + .toggle-slider::before {
transform: translateX(24px);
}
Accessibility Considerations
Custom CSS inputs remain fully accessible when built correctly:
Keyboard navigation: Because the native <input> is hidden with opacity: 0 (not display: none or visibility: hidden), it still receives focus. Tab navigation, spacebar to toggle, and Enter to submit forms all work normally.
Screen readers: The native input’s checked state is communicated to screen readers automatically. The visible label text is associated via the for/id pair.
Focus indicators: Add a focus-visible style for keyboard users:
.custom-checkbox-input:focus-visible + .custom-checkbox-control {
outline: 2px solid #3B82F6;
outline-offset: 2px;
}
The generated CSS from this tool includes focus-visible styles for all input types.
ARIA: No ARIA attributes are needed — the native <input> already has the correct role (checkbox or radio). Adding role="checkbox" to a <div> requires JavaScript to manage aria-checked; this approach avoids that complexity entirely.
Tailwind CSS Integration
The generated CSS can be adapted to Tailwind using arbitrary values or @layer components:
/* In your global CSS, inside @layer components */
@layer components {
.custom-toggle {
@apply relative inline-block w-12 h-6 rounded-full bg-gray-300 transition-colors duration-300
has-[:checked]:bg-blue-500 cursor-pointer;
}
.custom-toggle::before {
@apply content-[''] absolute w-[18px] h-[18px] top-[3px] left-[3px]
bg-white rounded-full transition-transform duration-300;
}
.custom-toggle:has(input:checked)::before {
@apply translate-x-6;
}
}
Note: The has() selector approach (CSS Selectors Level 4) requires Chrome 105+, Firefox 121+, Safari 15.4+.
React / Framework Integration
In React, use controlled state:
const [enabled, setEnabled] = useState(false);
return (
<label className="toggle-label">
<input
type="checkbox"
className="toggle-input"
checked={enabled}
onChange={(e) => setEnabled(e.target.checked)}
/>
<span className="toggle-slider" />
<span>{enabled ? 'Enabled' : 'Disabled'}</span>
</label>
);
The CSS handles all visual state — React only manages the checked prop.
Common Patterns
Settings Panel Toggle
/* Compact toggle for settings rows */
.settings-toggle .toggle-slider {
width: 36px;
height: 20px;
}
.settings-toggle .toggle-slider::before {
width: 14px;
height: 14px;
}
Checkbox Group with Indeterminate State
/* Indeterminate state (set via JavaScript: input.indeterminate = true) */
.custom-checkbox-input:indeterminate + .custom-checkbox-control {
background: #3B82F6;
border-color: #3B82F6;
}
.custom-checkbox-input:indeterminate + .custom-checkbox-control::after {
display: block;
height: 0; /* Dash instead of checkmark */
top: 50%;
left: 20%;
width: 60%;
transform: none;
}
Disabled State
.custom-checkbox-input:disabled + .custom-checkbox-control {
opacity: 0.4;
cursor: not-allowed;
}
.custom-checkbox-label:has(input:disabled) {
cursor: not-allowed;
opacity: 0.6;
}
Browser Support
The techniques used in the generated CSS are supported in all modern browsers:
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
opacity: 0 input hiding | All | All | All | All |
:checked pseudo-class | All | All | All | All |
CSS transition | All | All | All | All |
:focus-visible | 86+ | 85+ | 15.4+ | 86+ |
Adjacent sibling (+) | All | All | All | All |
No JavaScript, no polyfills, no build step required.
Frequently Asked Questions
Why hide the input with opacity: 0 instead of display: none?
display: none removes the element from the accessibility tree — screen readers won’t find it, keyboard focus skips it, and it won’t submit with a form. opacity: 0 with position: absolute; width: 0; height: 0 hides it visually while preserving all native behavior.
Can I use these styles with React or Vue?
Yes. The CSS classes work the same in any framework. In React, use controlled state with checked and onChange. The CSS handles all visual transitions — no framework-specific state is needed for the animation.
Do I need to add ARIA attributes?
No. When you hide the native <input> with opacity: 0 (not display: none), the native semantics are preserved. The browser reports the correct role and state to assistive technology automatically.
How do I make a toggle default to checked (on)?
Add the checked attribute to the HTML input: <input type="checkbox" checked />. In React, set defaultChecked for uncontrolled or checked={true} for controlled.
Can I use these styles without a <label> wrapper?
The + (adjacent sibling) CSS selector requires the <span> control to immediately follow the <input> inside the same parent. If you can’t use a wrapping <label>, use <input id="x"> with a separate <label for="x"> and place the control span between them — but the wrapper approach shown here is cleaner.
Is there a performance cost to CSS transitions on checkboxes?
No. CSS transition on background-color and transform properties is GPU-accelerated in modern browsers and has negligible performance impact.
Does this tool send my data to a server? No. All CSS generation happens entirely in your browser using JavaScript. No data is transmitted to any server.