Styling checkbox with Tailwind

React Checkbox component styling

Marek Rozmus
6 min readMar 25, 2023
Photo by Thomas Bormans on Unsplash

This time some pure frontend thing — styling checkbox with Tailwind. In the checkbox element we cannot just change some part of the styling (like e.g. border), whole checkbox needs to be restyled from scratch. The default styling needs to be cleared and it needs to be built step by step.

The whole code can be found in this GitHub repository: https://github.com/marekrozmus/blog-styling-react-checkbox-with-tailwind

Here is the Tailwind styled React component:

How to make style step by step.

You are here? So it looks like you want to know more — great. I will try to cover all gotchas that you can encounter during the styling process.

The HTML checkbox (more precisely input tag) appearance depends on the browser and the operating system. This is comparison of the default checkbox (on my system and browser) and some styled ones:

To make the checkbox consistent for webpage, the default styling needs to be removed and replaced with new one.

Here is the code for default checkbox code:

<div>
<input type="checkbox" id="some_id" />
<label htmlFor="some_id">This is the checkbox label</label>
</div>

Remove the default styles

To remove the default styling use appearance-none class.

<input type="checkbox" className="appearance-none" />

After using that class you the checkbox will just disappear. Now it is the time to design it.

Build the default input styles (the box)

Add the default width and height of the box, a border, background and some border radius.

<input type="checkbox" id="some_id" className="
appearance-none w-4 h-4 border-2 border-blue-500 rounded-sm bg-white"
/>

Fixing layouts

The label is very close to the box and the box is placed too high. Some flex settings will fix it. Here is a flex row with gap of 8px between the box and the label.

<div className="flex gap-2">
<input type="checkbox" id="some_id" className="
appearance-none w-4 h-4 border-2 border-blue-500 rounded-sm bg-white
mt-1"
/>
<label htmlFor="some_id">This is the checkbox label</label>
</div>

The box also got a margin top of 4px. This alignment will depend on the font that is used and the size of it (also line height). So these elements needs to be tuned to get desired results.

NOTE: In case of one line labels also a flex items alignment could be used but when the label has more than two lines (or can break on some screen sizes) then the following problem appears:

So remember to test your components with a lot of text and also a non-breaking long imagined words ;)

Fixing layout once again

The flex elements got shrink value set but default. In some cases the box might shrink if there won’t be enough space for it. To prevent it shrink-0 class must be added.

Without shrink-0 class

Adding checked state

When the checkbox is checked the background of the box will be blue and an SVG icon will be rendered. The checked modifier needs to be used as a prefix to classes:

<div className="flex gap-2">
<input type="checkbox" id="some_id" className="
appearance-none w-4 h-4 border-2 border-blue-500 rounded-sm bg-white
mt-1 shrink-0
checked:bg-blue-800 checked:border-0"
/>
<label htmlFor="some_id">This is the checkbox label</label>
</div>

The SVG icon needs to be rendered on the box so we must use the relative and absolute classes. The size of the SVG is set same as for the input box (including the top margin).

The icon should be displayed only if the checkbox is checked. To link the styles, the peer Tailwind class may be used in the input.

Then in SVG, the proper class will be applied only if the checkbox is checked. The SVG is hidden by default and only shown when the input tag is checked — hidden peer-checked:block classes.

<div className="flex gap-2">
<input type="checkbox" id="some_id" className="
relative peer shrink-0
appearance-none w-4 h-4 border-2 border-blue-500 rounded-sm bg-white
mt-1
checked:bg-blue-800 checked:border-0"
/>
<label htmlFor="some_id">This is the checkbox label</label>
<svg
className="
absolute
w-4 h-4 mt-1
hidden peer-checked:block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</div>

Fixing the click events

The current implementation so far checks and unchecks the box only when the label is clicked. When the box is clicked, nothing happens.

The problem here is that the SVG is rendered over the input and blocks the click events. The events just do not get through to the input element.

To fix it the pointer-events-none class needs to be added to the SVG classes list.

<svg
className="
absolute
w-4 h-4 mt-1
hidden peer-checked:block
pointer-events-none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline>
</svg>

Adding focused and disabled states

Tailwind got other pseudo classes that can be used to style specific states. Here is an example of styling the focused and disabled state. One detail that it is good to remember is the outline-none class. On some browsers it is easy to miss that outline and if not disabled may look our component odd sometimes.

<input type="checkbox" id="some_id" className="
relative peer shrink-0
appearance-none w-4 h-4 border-2 border-blue-500 rounded-sm bg-white
mt-1
checked:bg-blue-800 checked:border-0
focus:outline-none focus:ring-offset-0 focus:ring-2 focus:ring-blue-100
disabled:border-steel-400 disabled:bg-steel-400
"
/>

BONUS: The heart checkbox

You made it so far? The you should have your checkbox styled. Here is an another example of the heart checkbox. In this case no additional box is created, just the SVG icon which is filled or not depends on the checkbox state.

And that’s it — now you know how to style checkbox with tailwind classes.

Check also my other stories:

--

--