DEV Community

Cover image for Customizing radio buttons and checkboxes with CSS
Joohyun "Steve" Kim
Joohyun "Steve" Kim

Posted on

Customizing radio buttons and checkboxes with CSS

Disclaimer: There's an unnecessarily long introduction that I couldn't stop myself from writing. I had no intention of baiting so feel free to skip to the TL;DR section below if you're just here for the examples.

How much CSS do front-end developers have to know? I've heard arguments for both sides, ranging anywhere from "it's an absolute must since CSS is directly related to what the user sees and interacts with which is ultimately all things front-end" to "I've been a front-end developer for over 18 years and I've never had to write a single line of CSS during my entire career." But despite which end of the spectrum your opinion aligns with, I think most of us can agree that having an additional skill under your belt never hurt anyone and, if not anything, it will add to your list of developer traits than take away from it.

Having said that, it's also probably worth mentioning that CSS unfortunately seems to be one of those things that are often neglected and end up being self-taught. It was covered very briefly in my bootcamp curriculum and the little amount that was introduced was kept to the basics. It is also rarely included in any of the project requirements nor is it held to a high standard during an assessment. For a newbie programmer like myself, I'd say the primary motivation behind learning and practicing more CSS usually stems from self-fulfillment and the desire to make your final product look as "professional-grade" as possible.

Furthermore, I think the technical side of CSS can sometimes feel disjointed from the ability to make a web application look aesthetic because it shares close boundaries with the creative/artistic realm of web design which requires a different skillset like knowing which shades of colors to use and how to make something look visually appealing to the user.

TL;DR

So if you're at all interested in this post it must mean you're looking to customize your input type radios and checkboxes purely using CSS. Let's start with radio buttons because they're slightly simpler.

Radio Buttons

original radio button example

customized radio button example

The first step is to change the radio buttons to our desired size.

input[type="radio"] {
  width: 220px;
  height: 160px;
}
Enter fullscreen mode Exit fullscreen mode

enlarged radio buttons

BOOM. Big radio buttons. Next we set the appearance property, which controls the native appearance of UI controls, to none. Then we set a border (or a background color, or an image, or a combination of all three), just anything to help us actually see something appear on the page. I also got rid of the Yes and No text because we want these to show up inside the button and not beside it. Then I added a border-radius to round the edges and a cursor: pointer to bring back the pointer effect when we hover.

input[type="radio"] {
  width: 220px;
  height: 160px;
  appearance: none;
  border: solid 1px #72978d;
  background-color: #f1f5f5;
  border-radius: 6px;
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

empty custom radio buttons

Finally, I had a decision to make about how I wanted to handle the inner text as well as the checkmark graphic when the button is selected. For this particular example, it felt easy enough to simply use CSS pseudo-classes and different images to achieve the desired effect.

I concluded that there were essentially four different states that I needed to account for and thus four different images:

  1. Yes [unselected]
  2. Yes [selected]
  3. No [unselected]
  4. No [selected]
input[type="radio"]:first-of-type {
  background-image: url('./images/yes-unselected.svg');
}
input[type="radio"]:first-of-type:checked {
  background-image: url('./images/yes-selected.svg');
  background-color: #d8e2e0;
}
input[type="radio"]:last-of-type {
  background-image: url('./images/no-unselected.svg');
}
input[type="radio"]:last-of-type:checked {
  background-image: url('./images/no-selected.svg');
  background-color: #d8e2e0;
}
Enter fullscreen mode Exit fullscreen mode

You can also use different pseudo selectors or any other way that's appropriate to your code as long as you are able to grab the correct elements in applying these styles. The key is to make the images the exact size you need so that they are a perfect fit, but even if they weren't, you'd be able to add properties like background-size, background-repeat: no-repeat, and background-position: center to ensure they show up nicely. And lastly, since I use a different background-color for the 'checked' state, I also added the same color to the hover effect for a better UX.

input[type="radio"]:hover {
  background-color: #d8e2e0;
}
Enter fullscreen mode Exit fullscreen mode

Checkboxes

So what about checkboxes then? Can we just do the same thing?
Almost, but not quite. The gist of it is the same but checkboxes behave differently in that you're unable to add a background image. This means you lose a lot of flexibility and it creates a challenge in designing the checkbox to your desired look. What you can do, however, is hide its display, give it an id, and point to that id from a label tag which can set background images. Take the following example:

customized checkbox example

I've got several checkboxes that each have an image, text, and an additional input field that appears in the same "box" once clicked. The HTML for one of these checkboxes looks like this:

<div>
   <input type="checkbox" name="beer" id="beer" checked={checked.beer} onChange={handleCheckbox} />
   <label htmlFor="beer">
      <span>Beer</span>
   </label>
   {checked.beer ? <input type="number" ... /> : null}
</div>
Enter fullscreen mode Exit fullscreen mode

(It's actually JSX, but you get the idea.)

I'm using some local state in React to keep the form controlled and using a ternary statement to render the number field conditionally, but that's a whole another topic so we'll dial that back and stick to just the CSS portion. Just note that the htmlFor property above is the JSX equivalent to using the for attribute in HTML labels.

So the div here is representing our "checkbox" so-to-speak and inside I've got the actual checkbox input which we will use CSS to hide, the label (which wraps a span of the text), and then the <input type="number"> field which, again, will render depending on whether the checkbox is checked.

Then I apply the following CSS:

div {
  width: 220px;
  min-height: 160px;
  border: solid 1px #72978d;
  border-radius: 6px;
}
div:hover {
  background-color: rgb(216, 226, 224);
}
input[type="checkbox"] {
  display: none;
}
label {
  width: 220px;
  height: 160px;
  display: inline-block;
  background-repeat: no-repeat;
  background-position: 50% 25%;
  cursor: pointer;
}
label > span {
  display: inline-block;
  margin-top: 100px;
}
label[for="beer"] {
  background-image: url('./images/icon-beer.svg');
}
input[type="checkbox"]:checked + label {
  background-image: url('./images/icon-checkmark.svg');
}
Enter fullscreen mode Exit fullscreen mode

Okay, so let's break this down.

  • We set the width and height on the div that will act as our container, but only use min-height so that it can expand if we need to display the number field below it.
  • We then set a different color on hover to give the user some feedback like we did with the radio buttons (this part's not so important).
  • Then we hide the actual checkbox using display: none and instead fill the entire div with the label by using the same width and height.
  • We also set its display to inline-block in order to use a block element box but without the line breaks so that it flows with surrounding content.
  • Then we make sure to prevent the image from its default behavior, which is to repeat in both directions, and use background-position to define the X and Y values (using 50% for the X value to center the image but 25% for the Y value to leave some space underneath for the span).
  • And of course we mustn't forget to bring back the mouse pointer with the cursor property.
  • Then for the span of text, we use display: inline-block again and set a top margin to properly position it under the image.
  • And finally we set two background images on the label: the default image we want to display and a different image when the checkbox is checked. (Pro-tip: Placing the label right after the hidden checkbox in the HTML makes it easy to use the adjacent sibling combinator CSS selector to target the label when the checkbox is in its checked state.)

Again, as with most things in the land of programming, there's likely more than one approach to produce the same results (sometimes with subtle differences between them). So I'm certain that my examples are not the only way to customize these fields; they are merely one implementation that has worked for me and I share them here hoping someone might find them useful.

Final Thoughts

To be fair, I tend to put off CSS until the very end in all of my personal projects and I would argue that a significant amount of people probably do as well (or maybe it's just me). But one thing I noticed is that the more familiar I become with styling in CSS and utilizing certain patterns, determining how I should structure the application does become slightly more instinctive. In other words, it becomes much easier to code for the future knowing that I will probably want certain containers in certain places to hold certain class names and so forth. Next I think I'll explore with some of the popular CSS frameworks that we might encounter in the industry and see how they compare. Feel free to leave me comments or suggestions!

Top comments (0)