In this article we'll be making little checkboxes like these:
I'll assume you've already got Tailwind up and running in your React/ Next.js project.
1. Install the Forms plugin.
...if you haven't already. This is required because Tailwind doesn't simply apply CSS classes to the check boxes - it replaces them with an SVG.
pnpm add @tailwindcss/forms
# Or
npm i @tailwindcss/forms
2. Import the Forms plugin.
1import forms from '@tailwindcss/forms';
2import { type Config } from 'tailwindcss';
3
4export default {
5 content: ['./src/**/*.{ts,tsx}'],
6 plugins: [forms],
7} satisfies Config;
3. Create a Checkbox component
This creates a functional checkbox in a nice default blue colour. I've used the clsx(opens in a new tab) package, which is a lovely little utility for keeping your class names organised, especially when you've got conditional logic involved.
1'use client'; // You only need this line in Next.js, not React
2import { ReactNode, useState, useEffect } from 'react';
3import clsx from 'clsx';
4
5export default function Checkbox({
6 checked = false,
7 onChange,
8 children,
9}: CheckboxProps) {
10 const [isChecked, setIsChecked] = useState(checked);
11
12 useEffect(() => {
13 setIsChecked(checked);
14 }, [checked]);
15
16 const handleChange = () => {
17 const newChecked = !isChecked;
18 setIsChecked(newChecked);
19 if (onChange) {
20 onChange(newChecked);
21 }
22 };
23
24 return (
25 <div className="flex items-center me-2">
26 <input
27 type="checkbox"
28 id={`checkbox-${children}`}
29 checked={isChecked}
30 onChange={handleChange}
31 className={clsx(
32 'w-6 h-6',
33 'bg-gray-100',
34 'border-gray-300',
35 'rounded',
36 'focus:ring-2',
37 'transition duration-150 ease-in-out',
38 )}
39 />
40 <label className="text-sm ms-2" htmlFor={`checkbox-${children}`}>
41 {children}
42 </label>
43 </div>
44 );
45}
4. Add some colours
Now we can add some colours to the checkboxes, though some of these are more confusing than you might think.
Unchecked background colour
This is simply the regular background property. I'm using bg-gray-100
Add a subtle hover style to indicate that it's interactive, such as bg-red-200
Checked style
To change the colour of the negative space around the check mark, use a text colour style, such as text-red-500
The tick shape is actually an SVG that Tailwind injects for you, so if you want to change the colour it's quite complicated. I haven't bothered as they look pretty cool with white.
Focus ring
Finally, change the colour of the focus ring with something like focus:ring-red-400
5. Create colour options
Now let's create a colour map with these options to keep our code organised.
1const colourMap = {
2 red: 'text-red-500 focus:ring-red-400 hover:bg-red-200',
3 orange: 'text-orange-500 focus:ring-orange-400 hover:bg-orange-200',
4 yellow: 'text-yellow-500 focus:ring-yellow-400 hover:bg-yellow-200',
5 green: 'text-green-500 focus:ring-green-400 hover:bg-green-200',
6 blue: 'text-blue-500 focus:ring-blue-400 hover:bg-blue-200',
7 indigo: 'text-indigo-500 focus:ring-indigo-400 hover:bg-indigo-200',
8 violet: 'text-violet-500 focus:ring-violet-400 hover:bg-violet-200',
9};
Then we'll add a union type to the Checkbox props, which will allow our IDE to display the list of options as we're calling the component.
1interface CheckboxProps {
2 colour: 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'indigo' | 'violet';
3 checked?: boolean;
4 onChange?: (checked: boolean) => void;
5 children: ReactNode;
6}
6. The code in full
That's it! Here's the component in full:
1'use client';
2import { ReactNode, useState, useEffect } from 'react';
3import clsx from 'clsx';
4
5interface CheckboxProps {
6 colour: 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'indigo' | 'violet';
7 checked?: boolean;
8 onChange?: (checked: boolean) => void;
9 children: ReactNode;
10}
11
12const colourMap = {
13 red: 'text-red-500 focus:ring-red-400 hover:bg-red-200',
14 orange: 'text-orange-500 focus:ring-orange-400 hover:bg-orange-200',
15 yellow: 'text-yellow-500 focus:ring-yellow-400 hover:bg-yellow-200',
16 green: 'text-green-500 focus:ring-green-400 hover:bg-green-200',
17 blue: 'text-blue-500 focus:ring-blue-400 hover:bg-blue-200',
18 indigo: 'text-indigo-500 focus:ring-indigo-400 hover:bg-indigo-200',
19 violet: 'text-violet-500 focus:ring-violet-400 hover:bg-violet-200',
20};
21
22export default function Checkbox({
23 colour,
24 checked = false,
25 onChange,
26 children,
27}: CheckboxProps) {
28 const [isChecked, setIsChecked] = useState(checked);
29
30 useEffect(() => {
31 setIsChecked(checked);
32 }, [checked]);
33
34 const handleChange = () => {
35 const newChecked = !isChecked;
36 setIsChecked(newChecked);
37 if (onChange) {
38 onChange(newChecked);
39 }
40 };
41
42 return (
43 <div className="flex items-center me-2">
44 <input
45 type="checkbox"
46 id={`checkbox-${children}`}
47 checked={isChecked}
48 onChange={handleChange}
49 className={clsx(
50 'w-6 h-6',
51 'bg-gray-100',
52 'border-gray-300',
53 'rounded',
54 'focus:ring-2',
55 'transition duration-150 ease-in-out',
56 colourMap[colour]
57 )}
58 />
59 <label className="text-sm ms-2" htmlFor={`checkbox-${children}`}>
60 {children}
61 </label>
62 </div>
63 );
64}
And here's how you'd call it from another page/ component:
1import Checkbox from './Checkbox';
2
3export default function Page() {
4 return <Checkbox colour="green">Tick me</Checkbox>;
5}