Pure CSS Custom Checkbox Style | Modern CSS Solutions (2024)

We'll create custom, cross-browser, theme-able, scalable checkboxes in pure CSS with the following:

  • currentColor and CSS custom properties for theme-ability
  • em units for relative sizing
  • use of pseudo elements for the :checked indicator
  • CSS grid layout to align the input and label

Many of the concepts here overlap with our custom styled radio buttons from episode 18, with the addition of styling for the :disabled state

Now available: my egghead video course Accessible Cross-Browser CSS Form Styling. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.

Checkbox HTML

In the radio buttons article, we explored the two valid ways to markup input fields. Much like then, we will select the method where the label wraps the input.

Here's our base HTML for testing both an unchecked and checked state:

<label class="form-control"> <input type="checkbox" name="checkbox" /> Checkbox</label><label class="form-control"> <input type="checkbox" name="checkbox-checked" checked /> Checkbox - checked</label>

Common Issues With Native Checkboxes

As with the radio buttons, the checkbox appearance varies across browsers.

Here's the base styles across (in order from left) Chrome, Firefox, and Safari:

Pure CSS Custom Checkbox Style | Modern CSS Solutions (1)

Also like with radio buttons, the checkbox doesn't scale along with the font-size.

Our solution will accomplish the following goals:

  • scale with the font-size provided to the label
  • gain the same color as provided to the label for ease of theme-ability
  • achieve a consistent, cross-browser design style, including :focus state
  • maintain keyboard and color contrast accessibility

Our styles will begin with the same variable and reset as used for the radio buttons

Label Styles

Our label uses the class of .form-control. The base styles we'll include here are font styles. Recall from earlier that the font-size will not yet have an effect on the visual size of the checkbox input.

CSS for ".form-control font styles"
.form-control { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1;}

We're using an abnormally large font-size just to emphasize the visual changes for purposes of the tutorial demo.

Our label is also the layout container for our design, and we're going to set it up to use CSS grid layout to take advantage of gap.

CSS for ".form-control grid layout"
.form-control { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em;}

Custom Checkbox Style

Alright, now we'll get into restyling the checkbox to be our custom control.

The original version of this tutorial demonstrated use of extra elements to achieve the desired effect. Thanks to improved support of appearance: none and with appreciation to Scott O'Hara's post on styling radio buttons and checkboxes, we can rely on pseudo elements instead!

Join my newsletter for article updates, CSS tips, and front-end resources!

Step 1: Hide the Native Checkbox Input

We need to reset the native checkbox input styles, but keep it interactive to enable proper keyboard interaction and also to maintain access to the :focus state.

To accomplish this, we only need to set appearance: none. This removes nearly all inherited browser styles and gives us access to styling the input's pseudo elements. Notice we have two additional properties to complete the reset.

CSS for "hiding the native checkbox input"
input[type="checkbox"] { /* Add if not using autoprefixer */ -webkit-appearance: none; appearance: none; /* For iOS < 15 to remove gradient background */ background-color: #fff; /* Not removed via appearance */ margin: 0;}

Worried about support? This combination of using appearance: none and the ability to style the input's pseudo elements has been supported since 2017 in Chrome, Safari, and Firefox, and in Edge since their switch to Chromium in May 2020.

Step 2: Custom Unchecked Checkbox Styles

For our custom checkbox, we'll update box styles on the base input element. This includes inheriting the font styles to ensure the use of em produces the desired sizing outcome, as well as using currentColor to inherit any update on the label's color.

We use em for the width, height, and border-width value to maintain the relative appearance. We're also customizing the border-radius with another em relative style.

CSS for "custom unchecked checkbox styles"
input[type="checkbox"] { appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em);}.form-control + .form-control { margin-top: 1em;}

Our style updates includes a rule to give some space between our checkboxes by applying margin-top with the help of the adjacent sibling combinator.

Finally, as discussed in our radio button tutorial, we do a small adjustment on the label vs. checkbox alignment using a transform to nudge it up half the width of the border.

Step 3: Styling :checked vs Unchecked State

To prepare for the incoming pseudo element, we first need to change the display behavior of the input to use grid.

input[type="checkbox"] { /* ...existing styles */ display: grid; place-content: center;}

This is the quickest way to align the :before to the horizontal and vertical center of our custom control.

It's now time to bring in our ::before pseudo element which will be styled in order to represent the :checked state. We create the :before element, including a transition and using transform hide it with scale(0):

input[type="checkbox"]::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color);}

Use of box-shadow instead of background-color will enable the state of the radio to be visible when printed (h/t Alvaro Montoro).

Finally, when the input is :checked, we make it visible with scale(1) with a nicely animated result thanks to the transition. Be sure to change the checkbox state to see the animation!

CSS for ":checked state styles"
input[type="checkbox"] { /* ...existing styles */ display: grid; place-content: center;}input[type="checkbox"]::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color);}input[type="checkbox"]:checked::before { transform: scale(1);}

High Contrast Themes and Forced Colors

As reviewed in the radio buttons tutorial, one more state we need to ensure our radio responds to is what you may hear referred to as "Windows High Contrast Mode" (WHCM). In this mode, the user's operating system swaps out color-related properties for a reduced palette which is an incoming part of the CSS spec called "forced-colors".

Since box-shadow is removed, we'll ensure the :checked state is visible by providing a background-color, which is normally removed in forced-colors mode, but will be retained if we use one of the defined forced colors. In this case, we're selecting CanvasText which will match the regular body text color.

Due to the style stacking order, our box-shadow that we've themed for use in regular mode is actually visuallly placed over the background-color, meaning we can use both without any further modifications.

CSS for "supporting forced-colors"
input[type="checkbox"]::before { /* ...existing styles */ /* Windows High Contrast Mode */ background-color: CanvasText;}

Creating the "Checkmark" Shape

Right now, the filled-in state is OK, but it would be ideal to have it shaped as a checkmark to match the more expected pattern.

We have a few options here, such as bringing in an SVG as a background image. However, that solution means losing access to CSS custom properties which we are relying on to "theme" our inputs.

Instead, we'll re-shape the default box by using the clip-path property. This property allows us to treat the pseudo element's box similar to a vector element being reshaped with the pen tool. We define coordinates to redraw the shape between. You can use this handy clip-path generator to draw your own shapes or instantly pick up common ones. We also use clip-path to create a custom select dropdown arrow.

As a matter of preference, I also alter the transform-origin to use a value of bottom left instead of the default of center to mimic a sort of "checking in" animation.

CSS for "creating a checkmark with clip-path"
input[type="checkbox"]::before { /* ...existing styles */ transform-origin: bottom left; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);}

Step 4: The :focus state

In the earlier version of this tutorial, we used box-shadow, but now we have two improved features for the humble outline. First, we can use outline-offset to create a bit of space between the input and the outline. Second, evergreen browsers now support outline following border-radius!

Remember: :focus is a temporary state, but it's very important that it is highly visible to ensure the accessibility of your form controls and other interactive elements.

CSS for ":focus state styles"
input[type="checkbox"]:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em);}

This concludes our critical styles for the checkbox. If you're interested in an additional method to style the label, check out the radio button tutorial to learn how to use :focus-within.

Styles For :disabled Checkboxes

One step not present in the radio buttons tutorial was styling for the :disabled state.

This will follow a similar pattern as for our previous states, with the change here mostly being to update the color to a grey. We first re-assign the main --form-control-color to the new --form-control-disabled variable. Then, set the color property to use the disabled color.

CSS for ":disabled state styles"
:root { --form-control-disabled: #959495;}input[type="checkbox"]:disabled { --form-control-color: var(--form-control-disabled); color: var(--form-control-disabled); cursor: not-allowed;}

We've also updated to set the cursor to not-allowed as an additional visual cue that these inputs are not presently interactive.

But we've hit a snag. Since the label is the parent element, we don't currently have a way in CSS alone to style it based on the :disabled state.

For a CSS-only solution, we need to create an add an extra class to the label when it is known that the checkbox is disabled. Since this state can't be changed by the user, this will generally be an acceptable additional step.

CSS for ":disabled state styles"
.form-control--disabled { color: var(--form-control-disabled); cursor: not-allowed;}

Demo

Here's a demo that includes the :disabled styles, and also shows how the power of CSS variables + the use of currentColor means we can re-theme an individual checkbox with a simple inline style. This is very useful for things like a quick change to an error state.

By Stephanie Eckles (@5t3ph)

Pure CSS Custom Checkbox Style | Modern CSS Solutions (2024)

FAQs

How to style custom checkbox CSS? ›

To have full control of the styling of a checkbox, you can hide the checkbox input and use the input's ::before pseudo element to create a custom-styled checkbox. Setting the CSS appearance property to “none” hides the checkbox input. The input is made into a flexbox with its contents centered within the set border.

How do I make a checkbox checked in CSS? ›

Checked CSS Selector

The :checked CSS pseudo-class selector is used to select or deselect an already selected element for styling them. We can use it with the help of checkbox element(<input type="checkbox">),option element (<option> in a <select>) and radio element(<input type="radio">).

How to make checkbox unclickable CSS? ›

Using the pointer-events property

This approach is using the pointer-events CSS property to the input tag which is representing the checkboxes. When this CSS property is given as design to the input tag, the checkbox is essentially read-only so you can view the checkbox but cannot interact with it.

How to change checkbox background CSS? ›

Change the checkbox background color in CSS by hiding the default checkbox and using custom styles with the ::before and ::after pseudo-elements. This involves creating a custom checkbox that looks and works just like a regular checkbox but with your own design.

How do I create a custom style in CSS? ›

First in your site hover over the element for which you want to write the CSS, right click on it and then click on Inspect Element. Now, docked at the bottom, you will see the HTML of the site and to the right of it you can see the CSS related to the selected HTML element( classes and IDs ).

How do I change the format of a checkbox? ›

To change size, color, or border style of the check box, select the Use a style to format text typed into the empty control box, and then click New Style. Under Formatting, select a font size for the check box. In the Color list, select a color. To select a different border, select Format > Border.

How to make a checkbox checked and disabled? ›

So for a checked checkbox:
  1. <input type="checkbox" checked="checked" disabled="disabled" /> <input type="hidden" name="fieldname" value="fieldvalue" /> Run code snippet. ...
  2. <input type="checkbox" disabled="disabled" /> Run code snippet. ...
  3. <p>Status: Implemented<input type="hidden" name="status" value="implemented" /></p>
Sep 30, 2008

How do I make a checkbox clickable? ›

To create an HTML checkbox with a clickable label, use the <label> element and associate it with the checkbox using the for attribute, matching the checkbox's id . This makes the label clickable, toggling the checkbox state when clicked. The clickable label means the checkbox gets on/off when the label is clicked.

What is the difference between disabled and readonly checkboxes? ›

The difference between disabled and readonly is that read-only controls can still function and are still focusable, whereas disabled controls can not receive focus and are not submitted with the form and generally do not function as controls until they are enabled.

What is the color of checked checkbox style? ›

Checked checkboxes display a green background, visually indicating selection. Checkmark styled using pseudo-elements, appearing inside the checkbox when selected.

How do I align a checkbox in CSS? ›

Method 1: By making the position of checkbox relative, set the vertical-align to the middle can align the checkboxes and their labels. Here, we have made the position of the checkbox relative to the label. So the checkboxes are aligned according to the label.

How to change the border color of a checkbox in CSS? ›

1. Using the border-color Property. The most straightforward method to change the border color of checkboxes is by directly applying the property in CSS. This technique involves targeting the checkbox element and specifying the desired border color.

How do I style a checkbox border in CSS? ›

1. Using the border-color Property. The most straightforward method to change the border color of checkboxes is by directly applying the border-color property in CSS. This technique involves targeting the checkbox element and specifying the desired border color.

How do I change the color of a tick in a checkbox in CSS? ›

Let's take a few simple steps:
  1. Hide the browser's default checkbox.
  2. Create a custom checkbox. We will use a pseudo-element :before for the substrate.
  3. On mouse-over, add a darkly on 10% background color.
  4. Style the checkmark/indicator, by pseudo-element :after.
  5. Show or hide the checkmark when checked.

How do you style a specific input in CSS? ›

If you only want to style a specific input type, you can use attribute selectors: input[type=text] - will only select text fields. input[type=password] - will only select password fields. input[type=number] - will only select number fields.

References

Top Articles
Latest Posts
Article information

Author: Errol Quitzon

Last Updated:

Views: 5720

Rating: 4.9 / 5 (79 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Errol Quitzon

Birthday: 1993-04-02

Address: 70604 Haley Lane, Port Weldonside, TN 99233-0942

Phone: +9665282866296

Job: Product Retail Agent

Hobby: Computer programming, Horseback riding, Hooping, Dance, Ice skating, Backpacking, Rafting

Introduction: My name is Errol Quitzon, I am a fair, cute, fancy, clean, attractive, sparkling, kind person who loves writing and wants to share my knowledge and understanding with you.