1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
|
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark light">
<link rel="icon" href="data:,">
<title>Dynamic Checkboxes</title>
<link href="/atom.xml" type="application/atom+xml" rel="alternate" title="Atom feed for blog posts" />
<link href="/rss.xml" type="application/rss+xml" rel="alternate" title="RSS feed for blog posts" />
<style>*{box-sizing:border-box;}body{font-family:sans-serif;line-height:1.33;margin:0 auto;max-width:650px;padding:1rem;}blockquote{background:rgba(0,0,0,0.1);border-left:4px solid;padding-left:5px;}img{max-width:100%;}pre{border:1px solid;overflow:auto;padding:5px;}table{text-align:left;width:100%;}.footnotes{font-size:90%;}</style>
</head>
<nav>
<a href="#menu">Menu ↓</a>
</nav>
<main>
<h1 id="dynamic-checkboxes">Dynamic Checkboxes</h1>
<p>2019-07-30</p>
<p>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.</p>
<p>What if we could make everyday checkboxes more beautiful <em>and</em> more intuitive? <em>It’s easier than you think</em>. We only need a small amount of CSS and JavaScript to make considerable improvements to your average checkbox UX.</p>
<p>Let’s get into it.</p>
<h2 id="what-we-are-building">What we are building</h2>
<p>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.</p>
<p><a href="https://codepen.io/bradleytaunt/pen/rXWEpy/">Live CodePen Example</a></p>
<h2 id="the-structure-html">The Structure (HTML)</h2>
<p>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):</p>
<pre><code><h2>Add-ons</h2>
<input class="checkbox-btn" name="checkbox-collection" id="checkbox-1" type="checkbox" value="49">
<label class="checkbox-label" for="checkbox-1">
<span>
White-labeled Domain
<em>Use your own custom domain with SSL security included.</em>
</span>
<span>$49/mo</span>
</label>
<input class="checkbox-btn" name="checkbox-collection" id="checkbox-2" type="checkbox" value="49">
<label class="checkbox-label" for="checkbox-2">
<span>
API Access
<em>Make API calls to perform custom serving and account actions.</em>
</span>
<span>$49/mo</span>
</label>
<input class="checkbox-btn" name="checkbox-collection" id="checkbox-3" type="checkbox" value="349">
<label class="checkbox-label" for="checkbox-3">
<span>
Priority Support
<em>A dedicated account manager to assist your team with ongoing deployments.</em>
</span>
<span>$349/mo</span>
</label>
<div class="total-cost">
<h2>Your Plan</h2>
<div>
<span>$</span>
<span id="total-cost-inner">0</span>
<input id="output" type="text" value="0" disabled/>
<span>/mo</span>
</div>
</div>
</code></pre>
<h3 id="the-checkbox-inputs-labels">The checkbox inputs & labels</h3>
<pre><code><!-- #1 -->
<input class="checkbox-btn" name="checkbox-collection" id="checkbox-1" type="checkbox" value="49">
<!-- #2 -->
<label class="checkbox-label" for="checkbox-1">
<!-- #2i -->
<span>
White-labeled Domain
<em>Use your own custom domain with SSL security included.</em>
</span>
<!-- #2ii -->
<span>$49/mo</span>
</label>
</code></pre>
<ol>
<li><p>This input will be hidden via <code>position:absolute</code> by default. All checkbox inputs need to share the same <code>name</code> value and all checkboxes require their our custom <code>id</code> that will link with the corresponding <code>for</code> value on the label.</p></li>
<li><p>This label needs it’s <code>for</code> value to correspond with it’s partnered checkbox.</p>
<ul>
<li>i) The first span holds the title and description information of the add-on</li>
<li>ii) The last span holds the cost associated with the current add-on</li>
</ul></li>
</ol>
<h3 id="the-total-cost-container-output">The total cost container output</h3>
<pre><code><!-- #1 -->
<div class="total-cost">
<h2>Your Plan</h2>
<!-- #2 -->
<div>
<!-- #2i -->
<span>$</span>
<!-- #2ii -->
<span id="total-cost-inner">0</span>
<!-- #2iii -->
<input id="output" type="text" value="0" disabled/>
<!-- #2iv -->
<span>/mo</span>
</div>
</div>
</code></pre>
<ol>
<li><p>A simple <code>div</code> with a class we can easily target later</p></li>
<li><p>A <code>div</code> parent container is needed to house all the total <code>spans</code> together (more on this when we get into the CSS)</p>
<ul>
<li>i) The first <code>span</code> holds the static currency symbol</li>
<li>ii) The second <code>span</code> is where our updated cost will be injected</li>
<li>iii) This input field is required for us to take-in the <code>value</code> of the associated <code>:checked</code> inputs and add them together. This current value is then used for the injection into the second <code>span</code></li>
<li>iv) The final <code>span</code> simply holds the static monthly duration content</li>
</ul></li>
</ol>
<p>All that’s all we need for the HTML!</p>
<h2 id="the-visuals-css">The Visuals (CSS)</h2>
<p>Again, lets take a look at the entire file before we break it down step-by-step:</p>
<pre><code>.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,<svg xmlns="http://www.w3.org/2000/svg" width="23.571429" height="23.571429" viewBox="0.000000 -47.142857 23.571429 23.571429"><path fill="lightgrey" d="M23.571429 -36.964286L23.571429 -33.750000C23.571429 -33.303571 23.415179 -32.924107 23.102679 -32.611607C22.790179 -32.299107 22.410714 -32.142857 21.964286 -32.142857L15.000000 -32.142857L15.000000 -25.178571C15.000000 -24.732143 14.843750 -24.352679 14.531250 -24.040179C14.218750 -23.727679 13.839286 -23.571429 13.392857 -23.571429L10.178571 -23.571429C9.732143 -23.571429 9.352679 -23.727679 9.040179 -24.040179C8.727679 -24.352679 8.571429 -24.732143 8.571429 -25.178571L8.571429 -32.142857L1.607143 -32.142857C1.160714 -32.142857 0.781250 -32.299107 0.468750 -32.611607C0.156250 -32.924107 0.000000 -33.303571 0.000000 -33.750000L0.000000 -36.964286C0.000000 -37.410714 0.156250 -37.790179 0.468750 -38.102679C0.781250 -38.415179 1.160714 -38.571429 1.607143 -38.571429L8.571429 -38.571429L8.571429 -45.535714C8.571429 -45.982143 8.727679 -46.361607 9.040179 -46.674107C9.352679 -46.986607 9.732143 -47.142857 10.178571 -47.142857L13.392857 -47.142857C13.839286 -47.142857 14.218750 -46.986607 14.531250 -46.674107C14.843750 -46.361607 15.000000 -45.982143 15.000000 -45.535714L15.000000 -38.571429L21.964286 -38.571429C22.410714 -38.571429 22.790179 -38.415179 23.102679 -38.102679C23.415179 -37.790179 23.571429 -37.410714 23.571429 -36.964286ZM23.571429 -36.964286"></path></svg>');
}
.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,<svg xmlns="http://www.w3.org/2000/svg" width="25.948661" height="19.888393" viewBox="2.025670 -40.011161 25.948661 19.888393"><path fill="white" d="M27.974330 -36.127232C27.974330 -35.680804 27.818080 -35.301339 27.505580 -34.988839L15.385045 -22.868304L13.108259 -20.591518C12.795759 -20.279018 12.416295 -20.122768 11.969866 -20.122768C11.523438 -20.122768 11.143973 -20.279018 10.831473 -20.591518L8.554688 -22.868304L2.494420 -28.928571C2.181920 -29.241071 2.025670 -29.620536 2.025670 -30.066964C2.025670 -30.513393 2.181920 -30.892857 2.494420 -31.205357L4.771205 -33.482143C5.083705 -33.794643 5.463170 -33.950893 5.909598 -33.950893C6.356027 -33.950893 6.735491 -33.794643 7.047991 -33.482143L11.969866 -28.543527L22.952009 -39.542411C23.264509 -39.854911 23.643973 -40.011161 24.090402 -40.011161C24.536830 -40.011161 24.916295 -39.854911 25.228795 -39.542411L27.505580 -37.265625C27.818080 -36.953125 27.974330 -36.573661 27.974330 -36.127232ZM27.974330 -36.127232"></path></svg>');
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;
}
}
</code></pre>
<h3 id="the-checkbox-label">The checkbox label</h3>
<pre><code>/*
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);
}
</code></pre>
<h3 id="the-custom-checkbox-input">The custom checkbox input</h3>
<p>We need to hide the browser’s default checkbox input and replace it with our own using pseudo selectors.</p>
<pre><code>/* 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,<svg xmlns="http://www.w3.org/2000/svg" width="23.571429" height="23.571429" viewBox="0.000000 -47.142857 23.571429 23.571429"><path fill="lightgrey" d="M23.571429 -36.964286L23.571429 -33.750000C23.571429 -33.303571 23.415179 -32.924107 23.102679 -32.611607C22.790179 -32.299107 22.410714 -32.142857 21.964286 -32.142857L15.000000 -32.142857L15.000000 -25.178571C15.000000 -24.732143 14.843750 -24.352679 14.531250 -24.040179C14.218750 -23.727679 13.839286 -23.571429 13.392857 -23.571429L10.178571 -23.571429C9.732143 -23.571429 9.352679 -23.727679 9.040179 -24.040179C8.727679 -24.352679 8.571429 -24.732143 8.571429 -25.178571L8.571429 -32.142857L1.607143 -32.142857C1.160714 -32.142857 0.781250 -32.299107 0.468750 -32.611607C0.156250 -32.924107 0.000000 -33.303571 0.000000 -33.750000L0.000000 -36.964286C0.000000 -37.410714 0.156250 -37.790179 0.468750 -38.102679C0.781250 -38.415179 1.160714 -38.571429 1.607143 -38.571429L8.571429 -38.571429L8.571429 -45.535714C8.571429 -45.982143 8.727679 -46.361607 9.040179 -46.674107C9.352679 -46.986607 9.732143 -47.142857 10.178571 -47.142857L13.392857 -47.142857C13.839286 -47.142857 14.218750 -46.986607 14.531250 -46.674107C14.843750 -46.361607 15.000000 -45.982143 15.000000 -45.535714L15.000000 -38.571429L21.964286 -38.571429C22.410714 -38.571429 22.790179 -38.415179 23.102679 -38.102679C23.415179 -37.790179 23.571429 -37.410714 23.571429 -36.964286ZM23.571429 -36.964286"></path></svg>');
}
/*
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,<svg xmlns="http://www.w3.org/2000/svg" width="25.948661" height="19.888393" viewBox="2.025670 -40.011161 25.948661 19.888393"><path fill="white" d="M27.974330 -36.127232C27.974330 -35.680804 27.818080 -35.301339 27.505580 -34.988839L15.385045 -22.868304L13.108259 -20.591518C12.795759 -20.279018 12.416295 -20.122768 11.969866 -20.122768C11.523438 -20.122768 11.143973 -20.279018 10.831473 -20.591518L8.554688 -22.868304L2.494420 -28.928571C2.181920 -29.241071 2.025670 -29.620536 2.025670 -30.066964C2.025670 -30.513393 2.181920 -30.892857 2.494420 -31.205357L4.771205 -33.482143C5.083705 -33.794643 5.463170 -33.950893 5.909598 -33.950893C6.356027 -33.950893 6.735491 -33.794643 7.047991 -33.482143L11.969866 -28.543527L22.952009 -39.542411C23.264509 -39.854911 23.643973 -40.011161 24.090402 -40.011161C24.536830 -40.011161 24.916295 -39.854911 25.228795 -39.542411L27.505580 -37.265625C27.818080 -36.953125 27.974330 -36.573661 27.974330 -36.127232ZM27.974330 -36.127232"></path></svg>');
border-color: mediumpurple;
}
</code></pre>
<h3 id="the-total-cost-container">The total cost container</h3>
<p>We only need some very basic flexbox styling for our bottom “total” container:</p>
<pre><code>.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;
}
</code></pre>
<h3 id="last-but-not-least---mobile">Last but not least - mobile</h3>
<p>Now we just ensure that on smaller devices our checkbox labels render nicely:</p>
<pre><code>@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;
}
}
</code></pre>
<p>That’s it for the styling!</p>
<h2 id="the-function-js">The Function (JS)</h2>
<p>As you can see below, we only need a very minor amount of JavaScript to accomplish our total cost “injection”.</p>
<pre><code>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;
}
}
}
</code></pre>
<p>That’s it! Feel free to play with the demo some more at the top of the post, or check out the <a href="https://codepen.io/bradleytaunt/pen/rXWEpy">CodePen source directly</a>.</p>
<footer role="contentinfo">
<h2>Menu Navigation</h2>
<ul id="menu">
<li><a href="/">Home</a></li>
<li><a href="/projects">Projects</a></li>
<li><a href="/uses">Uses</a></li>
<li><a href="/wiki">Wiki</a></li>
<li><a href="/resume">Resume</a></li>
<li><a href="/colophon">Colophon</a></li>
<li><a href="/now">Now</a></li>
<li><a href="/donate">Donate</a></li>
<li><a href="/atom.xml">RSS</a></li>
<li><a href="#top">↑ Top of the page</a></li>
</ul>
<small>
Built with <a href="https://git.sr.ht/~bt/barf">barf</a>. <br>
Maintained with ♥ for the web. <br>
Proud supporter of <a href="https://usefathom.com/ref/DKHJVX">Fathom</a> & <a href="https://nextdns.io/?from=74d3p3h8">NextDNS</a>. <br>
The content for this site is <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>.<br> The <a href="https://git.sr.ht/~bt/bt.ht">code for this site</a> is <a href="https://git.sr.ht/~bt/bt.ht/tree/master/item/LICENSE">MIT</a>.
</small>
</footer>
|