How to create custom checkbox using only HTML and CSS (no-js) #1

kyoshee
4 min readMay 29, 2022

Originally posted on kyoshee.com

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

Using only HTML and CSS create 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.

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

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

Hiding input

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

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%;
}
Custom checkbox

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");
}
Custom checkbox in :checked state

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;
}
Custom checkbox in :hover state
Custom checkbox in :active state
Custom checkbox in :checked and :focus state
Custom checkbox in :focus state
Custom checkbox in :disabled state

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 and image:

.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);
}

Example on jsfiddle.

Examples

<input> outside <label>, checkbox
<input> outside <label>, radio

<input> inside <label>, checkbox
<input> inside <label>, radio

<input> inside <label>, checkbox, different style
<input> inside <label>, radio different style

Conclusion

Now we’re able to use pseudoelements, state selectors and neighbour CSS selectors. I hope this knowledge will help you!

Thank you for reading,
kyoshee

--

--