How to build custom checkbox with HTML and CSS without JavaScript #1

On one of my first interviews I’ve had interesting task on layouts:

Using only HTML and CSS build a custom checkbox, so its’ original image is not displayed but it still had functionality (checked — unchecked). Instead of original checkbox image insert custom image or text that shows current checkbox state and giving proper functionality.

My subconscious told me something about pseudoelements but even googling didn’t help me to do a task in 5 minutes, after which interviewer ended call saying “Wait for our feedback”. But I didn’t get upset. Instead, I improved my skill, so here it is: 2 ways to create custom checkbox.

First way

It is suitable for layout where input connected with label trough an id and for properties.

<input class="custom-checkbox"
       id="myCheckbox"
       type="checkbox" />
<label for="myCheckbox">Checkbox</label>
component preview

Order of the elements is important because CSS selectors depend on it.

Hiding <input/>

.custom-checkbox {
    position: absolute;
    z-index: -1;
    opacity: 0;
}
component preview

Important thing about using opacity: 0 instead of display: none is that with opacity we can get focus state of the input element for styling.

Creating fake checkbox

.custom-checkbox + label {
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

.custom-checkbox + label::before {
    content: '';
    display: inline-block;
    width: 1em;
    height: 1em;
    flex-shrink: 0;
    flex-grow: 0;
    border: 1px solid #c3c3c3;
    border-radius: 0.25em;
    margin-right: 0.5em;
    background-repeat: no-repeat;
    background-position: center center;
    background-size: 50% 50%;
}
component preview

Firstly, we verticaly aligning flag with align-items: center for flex containers.

With pseudoelement ::before we create imitation of a checkbox. For visibility paint its’ borders. Properties background-repeat, -position and -size define position on flag in checked state.

Pseudoelement styles in :checked state

.custom-checkbox:checked + label::before {
    border-color: blue;
    background-color: blue;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e");
}
component preview

Styles for :hover, :active, :focus and :disabled states

.custom-checkbox:not(:disabled):not(:checked) + label:hover::before {
    border-color: rgba(0, 0, 255, 0.33);
}

.custom-checkbox:not(:disabled):active + label::before {
    background-color: rgba(0, 0, 255, 0.66);
}

.custom-checkbox:focus + label::before {
    box-shadow: 0 0 0 0.2rem rgba(0, 0, 255, 0.125);
}

.custom-checkbox:focus:not(:checked) + label::before {
    border-color: #c3c3c3;
}

.custom-checkbox:disabled + label::before {
    background-color: black;
}

component preview

Second way

Second way is suitable for a layout where input in placed inside label:

<label class="custom-checkbox">
    <input type="checkbox">
</label>

With this layout we should add span element right after input:

<label class="container">Checkbox
    <input type="checkbox" checked="checked">
    <span class="checkmark"></span>
</label>

Let’s create a simple checkbox without accounting for different states and with a fixed image position.

label (container) styles:

.container {
    display: block;
    position: relative;
    padding-left: 35px;
    margin-bottom: 12px;
    cursor: pointer;
    font-size: 22px;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

Hiding default input:

.container input {
    position: absolute;
    z-index: -1;
    opacity: 0;
}

Drawing custom checkbox inside span element:

.checkmark {
    position: absolute;
    top: 0;
    left: 0;
    height: 25px;
    width: 25px;
    background-color: #eee;
}

Styling span in :hover and :checked states:

.container:hover input ~ .checkmark {
    background-color: #ccc;
}

.container input:checked ~ .checkmark {
    background-color: #2196F3;
}

Adding pseudoelement for checkmark:

.checkmark:after {
    content: "";
    position: absolute;
    display: none;
}

.container input:checked ~ .checkmark:after {
    display: block;
}

.container .checkmark:after {
    left: 9px;
    top: 5px;
    width: 5px;
    height: 10px;
    border: solid white;
    border-width: 0 3px 3px 0;
    -webkit-transform: rotate(45deg);
    -ms-transform: rotate(45deg);
    transform: rotate(45deg);
}
component preview

Examples

input outside label, type="checkbox" input outside label, type="radio" input inside label, type="checkbox" input inside label, type="radio" input inside label, type="checkbox", different style input inside label, type="radio", different style
$ cd ..