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
|
<!doctype html>
<html lang="en" id="top">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<title>Easy Custom Radio Inputs</title>
<link href="https://bt.ht/atom.xml" type="application/atom+xml" rel="alternate" title="Atom feed for blog posts" />
<style>*{box-sizing:border-box;}body{font-family:sans-serif;margin:0 auto;max-width:650px;padding:1rem;}img{max-width:100%;}pre{overflow:auto;}table{text-align:left;width:100%;}</style>
</head>
<nav>
<a href="#menu">Menu ↓</a>
</nav>
<main>
<h1>Easy Custom Radio Inputs</h1>
<p>2019-01-21</p>
<p>Default radio inputs are notoriously horrible looking and are something designers tend to over-think when trying to customize them. Let's walk through how to create custom radio buttons with <em>pure CSS</em>, while still preserving performance and accessibility.</p>
<h2>The Final Product</h2>
<p>This is what we will be designing:</p>
<p><img src="/public/images/custom-radio-inputs.png" alt="Custom radio inputs" /></p>
<p><a href="https://codepen.io/bradleytaunt/pen/oNjwMyX">Live CodePen Example</a></p>
<hr />
<h2>The bones of our radio inputs (HTML)</h2>
<pre><code><input class="radio-btn" name="radio-collection" id="radio-1" type="radio">
<label class="radio-label" for="radio-1"><span>I am very satisfied</span></label>
<input class="radio-btn" name="radio-collection" id="radio-2" type="radio">
<label class="radio-label" for="radio-2"><span>I am satisfied</span></label>
<input class="radio-btn" name="radio-collection" id="radio-3" type="radio">
<label class="radio-label" for="radio-3"><span>I am indifferent</span></label>
<input class="radio-btn" name="radio-collection" id="radio-4" type="radio">
<label class="radio-label" for="radio-4"><span>I am unsatisfied</span></label>
<input class="radio-btn" name="radio-collection" id="radio-5" type="radio">
<label class="radio-label" for="radio-5"><span>I am very unsatisfied</span></label>
</code></pre>
<p>I know it looks like a lot is going on here, but it's pretty straightforward so let's unpackage line by line:</p>
<h3>Radio inputs</h3>
<pre><code><input class="radio-btn" name="radio-collection" id="radio-1" type="radio">
</code></pre>
<p>This is the default <code>radio</code> input. We give it:</p>
<ul>
<li>a <code>name</code> (inputs with a shared <code>name</code> are grouped together)</li>
<li>an <code>id</code> (so our label can target this input)</li>
<li>a <code>class</code> (so we can style it later)</li>
</ul>
<p><strong>Important</strong>: be sure to have a unique <code>id</code> for each input so your labels don't end up connected to multiple radios. In this demo we are simply incrementing them by one.</p>
<h3>Labels</h3>
<p>Adding the labels is fairly straightforward, we just include the corresponding input's <code>id</code> in the label's <code>for</code> attribute. The label content is wrapped in a <code>span</code> - which I will explain the reasoning for later.</p>
<p>For styling purposes we also add the <code>radio-label</code> class.</p>
<pre><code><label class="radio-label" for="radio-1"><span>I am very satisfied</span></label>
</code></pre>
<p>This is looking pretty terrible - but that's nothing some good ol' CSS can't fix!</p>
<h2>The flesh of our radio inputs (CSS)</h2>
<p>First we give some basic styling to our <code>label</code> and <code>input</code> classes (along with hover states). The <code>radio</code> element is actually hidden from view, but by using the <code>visibility</code> attribute we still keep it accessible for screen-readers.</p>
<pre><code>.radio-label {
background: white;
border: 1px solid #eee;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
cursor: pointer;
display: inline-block;
font-weight: 600;
margin: 0 auto 10px;
/* This 65px padding makes room for the custom input */
padding: 20px 20px 20px 65px;
position: relative;
transition: .3s ease all;
width: 100%;
}
.radio-label:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.05);
}
.radio-btn {
position: absolute;
visibility: hidden;
}
</code></pre>
<p>Remember that <code>span</code> element inside the label? We set it's <code>user-select</code> property to <code>none</code> so we avoid any possible issue with the user selecting the text on-click:</p>
<pre><code>.radio-label span {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
</code></pre>
<p>Next we include the default empty selection element (to mimic the original radio input) via a pseudo element:</p>
<pre><code>.radio-label:before {
background: #eee;
border-radius: 50%;
content:'';
height: 30px;
left: 20px;
position: absolute;
/* Half the height of it's parent minus half of it's own height */
top: calc(50% - 15px);
transition: .3s ease background-color;
width: 30px;
}
</code></pre>
<h2>A Few Final Steps</h2>
<p>The final step is adding the custom styling for when an <code>input</code> item is selected (<code>:checked</code>).</p>
<p>You will notice the use of a <code>base64</code> element for the custom checkmark - feel free to subsitute this for an actual image or none at all (this is just my personal design preference).</p>
<pre><code>.radio-btn:checked + .radio-label {
background: #ECF5FF;
border-color: #4A90E2;
}
.radio-btn:checked + .radio-label:before {
background-color: #4A90E2;
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNiIgaGVpZ2h0PSIyMCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIyLjAyOTY4IC00MC4wOTAzIDI2IDIwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48IS0tR2VuZXJhdGVkIGJ5IElKU1ZHIChodHRwczovL2dpdGh1Yi5jb20vaWNvbmphci9JSlNWRyktLT48cGF0aCBkPSJNMjcuOTc0MywtMzYuMTI3MmMwLDAuNDQ2NDI4IC0wLjE1NjI1LDAuODI1ODkzIC0wLjQ2ODc1LDEuMTM4MzlsLTEyLjEyMDUsMTIuMTIwNWwtMi4yNzY3OSwyLjI3Njc5Yy0wLjMxMjUsMC4zMTI1IC0wLjY5MTk2NCwwLjQ2ODc1IC0xLjEzODM5LDAuNDY4NzVjLTAuNDQ2NDI4LDAgLTAuODI1ODkzLC0wLjE1NjI1IC0xLjEzODM5LC0wLjQ2ODc1bC0yLjI3Njc5LC0yLjI3Njc5bC02LjA2MDI3LC02LjA2MDI3Yy0wLjMxMjUsLTAuMzEyNSAtMC40Njg3NSwtMC42OTE5NjUgLTAuNDY4NzUsLTEuMTM4MzljMCwtMC40NDY0MjkgMC4xNTYyNSwtMC44MjU4OTMgMC40Njg3NSwtMS4xMzgzOWwyLjI3Njc5LC0yLjI3Njc5YzAuMzEyNSwtMC4zMTI1IDAuNjkxOTY1LC0wLjQ2ODc1IDEuMTM4MzksLTAuNDY4NzVjMC40NDY0MjksMCAwLjgyNTg5MywwLjE1NjI1IDEuMTM4MzksMC40Njg3NWw0LjkyMTg4LDQuOTM4NjJsMTAuOTgyMSwtMTAuOTk4OWMwLjMxMjUsLTAuMzEyNSAwLjY5MTk2NCwtMC40Njg3NSAxLjEzODM5LC0wLjQ2ODc1YzAuNDQ2NDI4LDAgMC44MjU4OTMsMC4xNTYyNSAxLjEzODM5LDAuNDY4NzVsMi4yNzY3OCwyLjI3Njc5YzAuMzEyNSwwLjMxMjUgMC40Njg3NSwwLjY5MTk2NCAwLjQ2ODc1LDEuMTM4MzlaIiB0cmFuc2Zvcm09InNjYWxlKDEuMDAxOTgpIiBmaWxsPSIjZmZmIj48L3BhdGg+PC9zdmc+');
background-repeat: no-repeat;
background-position: center;
background-size: 15px;
}
</code></pre>
<p><strong>And that's it.</strong></p>
<p>For easier reference the entire CSS file can be found below:</p>
<pre><code>.radio-label {
background: white;
border: 1px solid #eee;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
cursor: pointer;
display: inline-block;
font-weight: 600;
margin: 0 auto 10px;
padding: 20px 20px 20px 65px;
position: relative;
transition: .3s ease all;
width: 100%;
}
.radio-label:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.05);
}
.radio-label:before {
background: #eee;
border-radius: 50%;
content:'';
height: 30px;
left: 20px;
position: absolute;
top: calc(50% - 15px);
transition: .3s ease background-color;
width: 30px;
}
.radio-label span {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.radio-btn {
position: absolute;
visibility: hidden;
}
.radio-btn:checked + .radio-label {
background: #ECF5FF;
border-color: #4A90E2;
}
.radio-btn:checked + .radio-label:before {
background-color: #4A90E2;
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNiIgaGVpZ2h0PSIyMCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIyLjAyOTY4IC00MC4wOTAzIDI2IDIwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48IS0tR2VuZXJhdGVkIGJ5IElKU1ZHIChodHRwczovL2dpdGh1Yi5jb20vaWNvbmphci9JSlNWRyktLT48cGF0aCBkPSJNMjcuOTc0MywtMzYuMTI3MmMwLDAuNDQ2NDI4IC0wLjE1NjI1LDAuODI1ODkzIC0wLjQ2ODc1LDEuMTM4MzlsLTEyLjEyMDUsMTIuMTIwNWwtMi4yNzY3OSwyLjI3Njc5Yy0wLjMxMjUsMC4zMTI1IC0wLjY5MTk2NCwwLjQ2ODc1IC0xLjEzODM5LDAuNDY4NzVjLTAuNDQ2NDI4LDAgLTAuODI1ODkzLC0wLjE1NjI1IC0xLjEzODM5LC0wLjQ2ODc1bC0yLjI3Njc5LC0yLjI3Njc5bC02LjA2MDI3LC02LjA2MDI3Yy0wLjMxMjUsLTAuMzEyNSAtMC40Njg3NSwtMC42OTE5NjUgLTAuNDY4NzUsLTEuMTM4MzljMCwtMC40NDY0MjkgMC4xNTYyNSwtMC44MjU4OTMgMC40Njg3NSwtMS4xMzgzOWwyLjI3Njc5LC0yLjI3Njc5YzAuMzEyNSwtMC4zMTI1IDAuNjkxOTY1LC0wLjQ2ODc1IDEuMTM4MzksLTAuNDY4NzVjMC40NDY0MjksMCAwLjgyNTg5MywwLjE1NjI1IDEuMTM4MzksMC40Njg3NWw0LjkyMTg4LDQuOTM4NjJsMTAuOTgyMSwtMTAuOTk4OWMwLjMxMjUsLTAuMzEyNSAwLjY5MTk2NCwtMC40Njg3NSAxLjEzODM5LC0wLjQ2ODc1YzAuNDQ2NDI4LDAgMC44MjU4OTMsMC4xNTYyNSAxLjEzODM5LDAuNDY4NzVsMi4yNzY3OCwyLjI3Njc5YzAuMzEyNSwwLjMxMjUgMC40Njg3NSwwLjY5MTk2NCAwLjQ2ODc1LDEuMTM4MzlaIiB0cmFuc2Zvcm09InNjYWxlKDEuMDAxOTgpIiBmaWxsPSIjZmZmIj48L3BhdGg+PC9zdmc+');
background-repeat: no-repeat;
background-position: center;
background-size: 15px;
}
</code></pre>
<hr />
<h2>But wait - we can get even fancier!</h2>
<p>Since this demo is based off a survey-type questionaire, wouldn't it be interesting to give the different selectable options their own styling based on their context? Take a look at the further customized version below:</p>
<p>We can do so by adding <code>positive</code>, <code>neutral</code> and <code>negative</code> class names to the radio inputs with their own respective properties:</p>
<pre><code>.radio-btn.positive:checked + .radio-label {
background: #EAFFF6;
border-color: #32B67A;
}
.radio-btn.positive:checked + .radio-label:before {
background-color: #32B67A;
}
.radio-btn.neutral:checked + .radio-label:before {
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAtMTUgMzAgOC41NzE0MyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PCEtLUdlbmVyYXRlZCBieSBJSlNWRyAoaHR0cHM6Ly9naXRodWIuY29tL2ljb25qYXIvSUpTVkcpLS0+PHBhdGggZD0iTTMwLC0xMi4zMjE0djMuMjE0MjljMCwwLjczNjYwNyAtMC4yNjIyNzcsMS4zNjcxOSAtMC43ODY4MywxLjg5MTc0Yy0wLjUyNDU1NCwwLjUyNDU1NCAtMS4xNTUxMywwLjc4NjgzMSAtMS44OTE3NCwwLjc4NjgzMWgtMjQuNjQyOWMtMC43MzY2MDcsMCAtMS4zNjcxOSwtMC4yNjIyNzcgLTEuODkxNzQsLTAuNzg2ODMxYy0wLjUyNDU1MywtMC41MjQ1NTMgLTAuNzg2ODMsLTEuMTU1MTMgLTAuNzg2ODMsLTEuODkxNzR2LTMuMjE0MjljMCwtMC43MzY2MDcgMC4yNjIyNzcsLTEuMzY3MTkgMC43ODY4MywtMS44OTE3NGMwLjUyNDU1NCwtMC41MjQ1NTMgMS4xNTUxMywtMC43ODY4MyAxLjg5MTc0LC0wLjc4NjgzaDI0LjY0MjljMC43MzY2MDcsMCAxLjM2NzE5LDAuMjYyMjc3IDEuODkxNzQsMC43ODY4M2MwLjUyNDU1MywwLjUyNDU1NCAwLjc4NjgzLDEuMTU1MTMgMC43ODY4MywxLjg5MTc0WiIgZmlsbD0iI2ZmZiI+PC9wYXRoPjwvc3ZnPg==');
}
.radio-btn.negative:checked + .radio-label {
background: #FFF2F2;
border-color: #E75153;
}
.radio-btn.negative:checked + .radio-label:before {
background-color: #E75153;
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIxLjg1MTg1IC0zOS42OTcgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjwhLS1HZW5lcmF0ZWQgYnkgSUpTVkcgKGh0dHBzOi8vZ2l0aHViLmNvbS9pY29uamFyL0lKU1ZHKS0tPjxwYXRoIGQ9Ik0yMS43Mjk5LC0yMy40NzFjMCwwLjQ0NjQyOCAtMC4xNTYyNSwwLjgyNTg5MyAtMC40Njg3NSwxLjEzODM5bC0yLjI3Njc5LDIuMjc2NzljLTAuMzEyNSwwLjMxMjUgLTAuNjkxOTY0LDAuNDY4NzUgLTEuMTM4MzksMC40Njg3NWMtMC40NDY0MjgsMCAtMC44MjU4OTMsLTAuMTU2MjUgLTEuMTM4MzksLTAuNDY4NzVsLTQuOTIxODcsLTQuOTIxODhsLTQuOTIxODgsNC45MjE4OGMtMC4zMTI1LDAuMzEyNSAtMC42OTE5NjQsMC40Njg3NSAtMS4xMzgzOSwwLjQ2ODc1Yy0wLjQ0NjQyOCwwIC0wLjgyNTg5MiwtMC4xNTYyNSAtMS4xMzgzOSwtMC40Njg3NWwtMi4yNzY3OSwtMi4yNzY3OWMtMC4zMTI1LC0wLjMxMjUgLTAuNDY4NzUsLTAuNjkxOTY1IC0wLjQ2ODc1LC0xLjEzODM5YzAsLTAuNDQ2NDI5IDAuMTU2MjUsLTAuODI1ODkzIDAuNDY4NzUsLTEuMTM4MzlsNC45MjE4OCwtNC45MjE4OGwtNC45MjE4OCwtNC45MjE4OGMtMC4zMTI1LC0wLjMxMjUgLTAuNDY4NzUsLTAuNjkxOTY0IC0wLjQ2ODc1LC0xLjEzODM5YzAsLTAuNDQ2NDI4IDAuMTU2MjUsLTAuODI1ODkzIDAuNDY4NzUsLTEuMTM4MzlsMi4yNzY3OSwtMi4yNzY3OGMwLjMxMjUsLTAuMzEyNSAwLjY5MTk2NCwtMC40Njg3NSAxLjEzODM5LC0wLjQ2ODc1YzAuNDQ2NDI5LDAgMC44MjU4OTMsMC4xNTYyNSAxLjEzODM5LDAuNDY4NzVsNC45MjE4OCw0LjkyMTg4bDQuOTIxODcsLTQuOTIxODhjMC4zMTI1LC0wLjMxMjUgMC42OTE5NjUsLTAuNDY4NzUgMS4xMzgzOSwtMC40Njg3NWMwLjQ0NjQyOSwwIDAuODI1ODkzLDAuMTU2MjUgMS4xMzgzOSwwLjQ2ODc1bDIuMjc2NzksMi4yNzY3OGMwLjMxMjUsMC4zMTI1IDAuNDY4NzUsMC42OTE5NjUgMC40Njg3NSwxLjEzODM5YzAsMC40NDY0MjkgLTAuMTU2MjUsMC44MjU4OTMgLTAuNDY4NzUsMS4xMzgzOWwtNC45MjE4OCw0LjkyMTg4bDQuOTIxODgsNC45MjE4OGMwLjMxMjUsMC4zMTI1IDAuNDY4NzUsMC42OTE5NjQgMC40Njg3NSwxLjEzODM5WiIgdHJhbnNmb3JtPSJzY2FsZSgxLjAwNTYxKSIgZmlsbD0iI2ZmZiI+PC9wYXRoPjwvc3ZnPg==');
}
</code></pre>
<p>I hope this shows new designers that simple custom radio inputs aren't so hard to implement after-all and can actually be pretty fun to design.</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>
|