[[!meta title="Dynamic Checkboxes"]]
[[!meta date="2019-07-30"]]
Checkboxes are used quite frequently on forms across the web. Whether you're selecting a pricing plan during a site's sign-up process or just simply selecting to opt-out from a newsletter, you have most likely interacted with some form of checkbox element.
What if we could make everyday checkboxes more beautiful *and* more intuitive? *It's easier than you think*. We only need a small amount of CSS and JavaScript to make considerable improvements to your average checkbox UX.
Let's get into it.
## What we are building
Take a look and play around with the CodePen below to get an idea of what we are going to build. The premise is a simple add-on pricing form which calculates the additional monthly total to the user in real-time.
[Live CodePen Example](https://codepen.io/bradleytaunt/pen/rXWEpy/)
## The Structure (HTML)
As always, we will start by breaking down the "bones" of the HTML structure for this checkbox form. Let's take a look at the HTML in it's entirety (don't worry, it is a lot more simple than it seems at first glance):
Add-ons
Your Plan
$0/mo
### The checkbox inputs & labels
1. This input will be hidden via `position:absolute` by default. All checkbox inputs need to share the same `name` value and all checkboxes require their our custom `id` that will link with the corresponding `for` value on the label.
2. This label needs it's `for` value to correspond with it's partnered checkbox.
- i) The first span holds the title and description information of the add-on
- ii) The last span holds the cost associated with the current add-on
### The total cost container output
Your Plan
$0/mo
1. A simple `div` with a class we can easily target later
2. A `div` parent container is needed to house all the total `spans` together (more on this when we get into the CSS)
- i) The first `span` holds the static currency symbol
- ii) The second `span` is where our updated cost will be injected
- iii) This input field is required for us to take-in the `value` of the associated `:checked` inputs and add them together. This current value is then used for the injection into the second `span`
- iv) The final `span` simply holds the static monthly duration content
All that's all we need for the HTML!
## The Visuals (CSS)
Again, lets take a look at the entire file before we break it down step-by-step:
.checkbox-label {
align-items: center;
background-color: none;
border: 1px solid lightgrey;
border-radius: 5px;
cursor: pointer;
display: flex;
font-weight: 600;
justify-content: space-between;
margin: 0 auto 10px;
padding: 20px 20px 20px 70px;
position: relative;
transition: .3s ease all;
width: 100%;
}
.checkbox-label span:last-child {
padding: 0 0 0 20px;
}
.checkbox-label:hover {
background-color: rgba(255,255,255,0.2);
}
.checkbox-label:before {
background-repeat: no-repeat;
background-position: center;
background-size: 15px;
border: 1px solid lightgrey;
border-radius: 50%;
content:'';
height: 30px;
left: 20px;
position: absolute;
top: calc(50% - 15px);
transition: .3s ease background-color;
width: 30px;
}
.checkbox-label:hover:before {
background-image:
url('data:image/svg+xml;utf8,');
}
.checkbox-label span {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.checkbox-label span em {
display: block;
font-size: 80%;
font-style: normal;
font-weight: 400;
line-height: 1.2;
}
.checkbox-btn {
position: absolute;
visibility: hidden;
}
.checkbox-btn:checked + .checkbox-label {
background-color: white;
border-color: mediumpurple;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.checkbox-btn:checked + .checkbox-label:before {
background-color: mediumpurple;
background-image:
url('data:image/svg+xml;utf8,');
border-color: mediumpurple;
}
.total-cost {
align-items: baseline;
border-top: 1px solid lightgrey;
display: flex;
justify-content: space-between;
margin-top: 40px;
padding: 40px 20px 0;
}
.total-cost div {
align-items: baseline;
display: flex;
}
.total-cost span:nth-child(1) {
align-self: flex-start;
padding-top: 5px;
}
.total-cost span:nth-child(2) {
font-size: 32px;
font-weight: bold;
}
.total-cost input {
display: none;
}
@media(max-width:480px) {
.checkbox-label {
align-items: flex-start;
flex-direction: column;
flex-wrap: wrap;
}
.checkbox-label span:last-child {
padding: 10px 0 0 0;
}
}
### The checkbox label
/*
This is the main element for each checkbox "container".
Inside it houses the title, description and price.
*/
.checkbox-label {
align-items: center;
background-color: none;
border: 1px solid lightgrey;
border-radius: 5px;
cursor: pointer;
display: flex;
font-weight: 600;
justify-content: space-between;
margin: 0 auto 10px;
padding: 20px 20px 20px 70px;
position: relative;
transition: .3s ease all;
width: 100%;
}
.checkbox-label:hover {
background-color: rgba(255,255,255,0.2);
}
/* Update the label styling when the input is :checked */
.checkbox-btn:checked + .checkbox-label {
background-color: white;
border-color: mediumpurple;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
### The custom checkbox input
We need to hide the browser's default checkbox input and replace it with our own using pseudo selectors.
/* Hide browser default input */
.checkbox-btn {
position: absolute;
visibility: hidden;
}
/* Our custom input checkbox */
.checkbox-label:before {
background-repeat: no-repeat;
background-position: center;
background-size: 15px;
border: 1px solid lightgrey;
border-radius: 50%;
content:'';
height: 30px;
left: 20px;
position: absolute;
top: calc(50% - 15px);
transition: .3s ease background-color;
width: 30px;
}
/*
Here we add a simple '+' icon on hover
to our custom pseudo element.
Adding it as an inline SVG gives us the
ability to fully customize it's styling
*/
.checkbox-label:hover:before {
background-image:
url('data:image/svg+xml;utf8,');
}
/*
When the checkbox input is :checked we need to
update the inline SVG to use a checkmark symbol
*/
.checkbox-btn:checked + .checkbox-label:before {
background-color: mediumpurple;
background-image:
url('data:image/svg+xml;utf8,');
border-color: mediumpurple;
}
### The total cost container
We only need some very basic flexbox styling for our bottom "total" container:
.total-cost {
align-items: baseline;
border-top: 1px solid lightgrey;
display: flex;
justify-content: space-between;
margin-top: 40px;
padding: 40px 20px 0;
}
.total-cost div {
align-items: baseline;
display: flex;
}
.total-cost span:nth-child(1) {
align-self: flex-start;
padding-top: 5px;
}
.total-cost span:nth-child(2) {
font-size: 32px;
font-weight: bold;
}
/*
This input is used in our JavaScript - look at the
function part of this post to understand why
*/
.total-cost input {
display: none;
}
### Last but not least - mobile
Now we just ensure that on smaller devices our checkbox labels render nicely:
@media(max-width:480px) {
/*
Avoids the inner label content from squishing together
and becoming unreadable
*/
.checkbox-label {
align-items: flex-start;
flex-direction: column;
flex-wrap: wrap;
}
.checkbox-label span:last-child {
padding: 10px 0 0 0;
}
}
That's it for the styling!
## The Function (JS)
As you can see below, we only need a very minor amount of JavaScript to accomplish our total cost "injection".
window.onload=function(){
// Place the default browser checkbox inputs into a variable
var inputs = document.getElementsByClassName('checkbox-btn')
// Now we loop through the inputs and check if they are
// greater than zero. If so, we run our function.
for (var i=0; i < inputs.length; i++) {
inputs[i].onchange = function() {
// Create `add` variable which takes the :checked input value
var add = this.value * (this.checked ? 1 : -1);
// We grab the current total value on our hidden input field and return it
// as a floating point number
// (since in this use case it will be a price number based on currency)
var new_total = parseFloat(document.getElementById('output').value);
// Now we simply add the existing total value with the newly ":checked" input value
var updated_total = document.getElementById('output').value=new_total + add
// Place the new updated total directly inside the `total-cost-inner` span element
document.getElementById('total-cost-inner').innerHTML = updated_total;
}
}
}
That's it! Feel free to play with the demo some more at the top of the post, or check out the [CodePen source directly](https://codepen.io/bradleytaunt/pen/rXWEpy).