Customizing radio buttons and checkboxes with CSS
Tags: css, html, webdev
Published: 2022-04-06 18:30:39

_**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mxyup5i96xha3ahd0ldk.png)

![customized radio button example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrapbeep9m0xvxark2nk.png)

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

```css
input[type="radio"] {
  width: 220px;
  height: 160px;
}
```

![enlarged radio buttons](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/knd3gzj72kzne7f7va53.png)

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.

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

![empty custom radio buttons](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t3bsacef8hcollx6c6d0.png)

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]

```css
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;
}
```

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.

```css
input[type="radio"]:hover {
  background-color: #d8e2e0;
}
```

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5yvohgdqv4earp9et3jx.png)

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:

```jsx
<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>
```
*(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:

```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');
}
```

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!