--- layout: post title: "Dynamic Checkboxes" 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).