DEV Community

Cover image for CSS Stylish Custom Checkbox and Radio Input
Mahmoud Elmahdi
Mahmoud Elmahdi

Posted on

CSS Stylish Custom Checkbox and Radio Input

Checkbox and radio input fields often used for web forms and the default visual appearance depend on the platform users are on and may look inconsistent on different browsers.

Back to the day we had to use JavaScript to customize these elements to have basic effects. Fortunately, now days there’s more than technique and different ways to customize the look and feel of checkbox and radio input fields entirely with CSS (w/o JavaScript) so we can deliver a better user experience with clean and nice UI.

In this article I'll show you a step-by-step approach on how to create a Stylish Custom Checkbox and Radio input fields.

Demo

Just so you have an idea of what the end effect is going to look like, here’s the final demo

Semantic HTML

First we need to layout our HTML

<label for="..." class="si si-*">
   <input type="..." id="..." />
   <span class="si-label">...</span>
</label>
Enter fullscreen mode Exit fullscreen mode

The label tag used to wrap input and span elements which represents our customized input fields. We'll be also creating a switcher and some extra (add-ons).

  • The label element's for attribute matches the id attribute of the input element (required)
  • si stands for "stylish input" followed by a prefixed hyphen class ( si-checkbox, si-radio and si-switcher) to specify the element we want to display.
  • si-label represents the label's text

HTML: Checkboxes

<label for="defaultCheckbox" class="si si-checkbox">
  <input type="checkbox" id="defaultCheckbox" />
  <span class="si-label">Default checkbox state</span>
</label>

<label for="checkedCheckbox" class="si si-checkbox">
  <input type="checkbox" id="checkedCheckbox" checked />
  <span class="si-label">Checked checkbox state</span>
</label>
Enter fullscreen mode Exit fullscreen mode

HTML: Radio buttons group

<label for="defaultRadio" class="si si-radio">
  <input type="radio" id="defaultRadio" name="radioGroup" />
  <span class="si-label">Default radio state</span>
</label>

<label for="checkedRadio" class="si si-radio">
  <input type="radio" id="checkedRadio" name="radioGroup" checked />
  <span class="si-label">Checked radio state</span>
</label>
Enter fullscreen mode Exit fullscreen mode

HTML: Switcher

<label for="defaultSwitcher" class="si si-switcher">
  <input type="checkbox" id="defaultSwitcher" />
  <span class="si-label">Default switcher state</span>
</label>

<label for="checkedSwitcher" class="si si-switcher">
  <input type="checkbox" id="checkedSwitcher" checked />
  <span class="si-label">Checked switcher state</span>
</label>
Enter fullscreen mode Exit fullscreen mode

HTML: Extras

Elements with data-onchecked="..." will be toggled based on the input's state. Available values "show" and "hide".

data-onchecked="show" will be hidden by default, on input:checked will become visible.

<label for="showOnChecked" class="si si-checkbox">
  <input type="checkbox" id="showOnChecked" />
  <span class="si-label">Show "I'm not a robot" on checked</span>
  <span data-onchecked="show">I'm not a robot</span>
</label>
Enter fullscreen mode Exit fullscreen mode

data-onchecked="hide" will be visible by default, on input:checked will become hidden.

<label for="hideOnChecked" class="si si-checkbox">
  <input type="checkbox" id="hideOnChecked" />
  <span class="si-label">Hide "Stylish" on checked</span>
  <span data-onchecked="hide">Stylish</span>
</label>
Enter fullscreen mode Exit fullscreen mode

Line across: by adding an extra line-across class to si-label on input:checked state, it will draw line across the the label's text

<label for="lineAcross" class="si si-checkbox">
  <input type="checkbox" id="lineAcross" />
  <span class="si-label line-across">Mark as done</span>
</label>
Enter fullscreen mode Exit fullscreen mode

CSS

Start by adding common style rules

/* custom properties (AKA css variables) */
.si {
  --color-label: #9aa6bf;
  --color-default: #dee5f2;
  --color-active: #0069ff;
  --rotate-default: 180deg;
  --rotate-active: 40deg;
  --border-size-checkmark: 2px;
  --border-size-box: 1px;
  --input-size: 20px;
  --guter: 15px;
}

/* basic reset */
.si,
.si *,
.si *::before,
.si *::after {
  box-sizing: border-box;
}

/* label */
.si {
  cursor: pointer;
  position: relative;
}

.si .si-label {
  display: inline-block;
  padding-left: var(--guter);
  color: var(--color-label);
  vertical-align: text-top;
}
Enter fullscreen mode Exit fullscreen mode

Next up we have to hide the input field. There are several ways to get this done:

.si > input { display: none; }
/* OR */ 
.si > input { opacity: 0; }
/* OR */ 
.si > input { visibility: hidden; }
Enter fullscreen mode Exit fullscreen mode

By applying any of these will make the input field inaccessible by screen readers. There's elegant way to hide elements with clip-path and keep them accessible

.si > input[type="checkbox"],
.si > input[type="radio"] {
  clip-path: polygon(0 0);
}
Enter fullscreen mode Exit fullscreen mode

Pseudo-element

We'll use ::before and ::after to represent our checkbox (square and checkmark) and radio (two circles)

/* change property values smoothly */
.si .si-label::before,
.si .si-label::after {
  transition: all 0.2s ease-in-out;
}

/* an element for both checkbox and radio */
.si .si-label::before {
  content: '';
  display: block;
  width: var(--input-size);
  height: var(--input-size);
  border: var(--border-size-box) solid var(--color-default);
  position: absolute;
  top: -3px;
  left: 0;
  transform: rotate(0deg) scale(1);
}

/* feedback when the user interacts with an element */
.si .si-label:hover::before {
  border-color: var(--color-active);
}
Enter fullscreen mode Exit fullscreen mode

CSS: Checkboxes

default state:

/* checkbox square */
.si.si-checkbox .si-label::before {
  border-radius: var(--border-size-checkmark);
}

/* checkbox checkmark */
.si.si-checkbox .si-label::after {
  content: '';
  display: block;
  width: 8px;
  height: 18px;
  border-width: 0 var(--border-size-checkmark) var(--border-size-checkmark) 0;
  border-style: solid;
  border-color: transparent var(--color-active) var(--color-active) transparent;
  position: absolute;
  top: -3px;
  left: 0;
  transform: rotate(var(--rotate-default)) scale(0);
}
Enter fullscreen mode Exit fullscreen mode

checked state:

/* checkbox square */
.si.si-checkbox > input:checked + .si-label::before {
  transform: rotate(var(--rotate-active)) scale(0);
}

/* checkmark */
.si.si-checkbox > input:checked + .si-label::after {
  left: 8px;
  transform: rotate(var(--rotate-active)) scale(1);
}
Enter fullscreen mode Exit fullscreen mode

CSS: Radio

default state:

/* circles, alignment */
.si.si-radio .si-label::before,
.si.si-radio .si-label::after {
  border-radius: 100%;
  top: -2px;
}

/* radio bullet circle */
.si.si-radio .si-label::after {
  content: '';
  display: block;
  position: absolute;
  width: 12px;
  height: 0;
  top: 100%;
  left: 4px;
}
Enter fullscreen mode Exit fullscreen mode

checked state:

.si.si-radio > input:checked + .si-label::after {
  background-color: #fff;
  height: 12px;
  top: 2px;
}

.si.si-radio > input:checked + .si-label::before {
  background-color:var(--color-active);
}
Enter fullscreen mode Exit fullscreen mode

CSS: Switcher

default state:

/* switcher label gutter */
.si.si-switcher .si-label {
  padding-left: 45px;
}

/* switcher rounded rectangle */
.si.si-switcher .si-label::before {
  content: '';
  width: 36px;
  height: 20px;
  border-radius: 20px;
  top: -2px;
}

/* switcher bullet */
.si.si-switcher .si-label::after {
  content: '';
  border-radius: 4px;
  width: 6px;
  height: 12px;
  background-color: var(--color-default);
  position: absolute;
  top: 2px;
  left: 7px;
}
Enter fullscreen mode Exit fullscreen mode

checked state:

.si.si-switcher > input:checked + .si-label::before {
  background-color: var(--color-active);
  border-color: var(--color-active);
}

.si.si-switcher > input:checked + .si-label::after {
  background-color: #fff;
  left: 24px;
}
Enter fullscreen mode Exit fullscreen mode

CSS: Extras

.si [data-onchecked="show"],
.si > input:checked ~ [data-onchecked="hide"] {
  display: none;
}

.si [data-onchecked="hide"],
.si > input:checked ~ [data-onchecked="show"] {
  display: inline-block;
}

.si > input:checked + .si-label.line-across {
  text-decoration: line-through;
}
Enter fullscreen mode Exit fullscreen mode

general sibling combinator "tilde" matches all data-onchecked="..." elements that are siblings of input:checked and appear after it.

Discussion (1)

Collapse
altsyset profile image
Alula TYC

This is neat, thanks a lot.