Home / Snippets / UI Components /

Custom checkbox

Styled with appearance: none — smooth transition, real check mark.

Widely Supported
uino-js

Quick implementation

/* HTML: <label><input type="checkbox" class="checkbox" /> Label</label> */

.checkbox {
  appearance: none;
  -webkit-appearance: none;
  width: 1.25rem;
  height: 1.25rem;
  border: 2px solid oklch(0.45 0.02 260);
  border-radius: 0.25rem;
  background: transparent;
  cursor: pointer;
  position: relative;
  transition: background 0.15s ease-out, border-color 0.15s ease-out;
}

.checkbox:checked {
  background: oklch(0.52 0.22 265);
  border-color: oklch(0.52 0.22 265);
}

.checkbox:checked::after {
  content: '';
  position: absolute;
  left: 0.3rem;
  top: 0.1rem;
  width: 0.35rem;
  height: 0.65rem;
  border: solid white;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.checkbox:focus-visible {
  outline: 2px solid oklch(0.72 0.19 265);
  outline-offset: 2px;
}

.checkbox:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building custom form controls.

Goal: A styled checkbox with custom check mark using appearance: none — no JavaScript.

Technical constraints:
- Use appearance: none to strip native styling.
- Check mark: ::after pseudo-element with rotated border trick.
- Checked state: background fills with accent color, check mark appears.
- Use oklch() for all colors, not hex or rgba().
- Focus-visible: 2px outline with 2px offset for keyboard navigation.
- Disabled state: reduced opacity, not-allowed cursor.

Framework variant (pick one):
A) Vanilla HTML + CSS only, using native <input type="checkbox">.
B) React component — accept checked, onChange, disabled, label props.

Edge cases to handle:
- Must remain accessible — use real <input> not div, wrapped in <label>.
- Indeterminate state styling if needed (optional).
- Right-to-left layout should not break check mark positioning.

Return HTML + CSS.

Why this matters in 2026

Native checkboxes look different on every OS and browser. appearance: none strips the default styling completely, letting you build a consistent visual across all platforms. Unlike old hidden-checkbox-plus-label hacks, this approach keeps the actual <input> visible and accessible.

The logic

appearance: none removes the browser's default checkbox rendering. The element keeps its semantic behavior — it's still a checkbox, it still toggles, it still works with forms. The ::after pseudo-element on :checked draws the check mark using the border rotation trick: a small rectangle with only a bottom and right border, rotated 45 degrees to form an L-shape that looks like a check mark.

Accessibility & performance

Always wrap the checkbox in a <label> — this makes the entire text clickable and associates the label with the input for screen readers. Never replace the checkbox with a <div> and click handlers. The :focus-visible outline ensures keyboard users can navigate the form. The check mark animation uses opacity and transform, both compositor-friendly.