Tailwind CSS is a CSS framework that gives you utility classes instead of pre-built components. In this article, we will write a reusable Button component that can have different variants and sizes while also being customizable.
Setting up a project
First, you need to install tailwind.
Install tailwind
Install the dependecies as dev dependecies.
# npm
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
# yarn
yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest
Create your configuration files
Next, generate your tailwind.config.js
and postcss.config.js
files:
# npm
npx tailwindcss init -p
# yarn
yarn tailwindcss init -p
Include Tailwind in your CSS
You need to add these 3 lines to your global/index CSS file.
@tailwind base;
@tailwind components;
@tailwind utilities;
Protip: If you are using VS Code you should install the Tailwind CSS IntelliSense
You can check if tailwind is working correctly by rendering this button element.
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Enable
</button>
If not working properly please refer to the documentation page.
Creating a button component
Let's create reusable button from the snippet earlier. I am using typescript for better prop validation.
// Button.tsx
import { forwardRef } from "react";
interface ButtonOptions {}
type Ref = HTMLButtonElement;
expor_t type ButtonProps = React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> &
ButtonOptions;
const Button = forwardRef<Ref, ButtonProps>((props, ref) => {
const { type = "button", children, ...rest } = props;
return (
<button
ref={ref}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
{...rest}
>
{children}
</button>
);
});
Button.displayName = "Button";
export default Button;
Awesome now we can use our button like this
// import Button from "../src/components/Button/Button";
<Button>Anything</Button>
But what if I wanted a red button on the home page. We can pass a bg-red-500
class to our button component but that would overwrite the className prop removing our button styles. So we need to merge our className props.
Merging classes
We can use a module called clsx
to merge our classes.
Our button component looks like this now.
const Button = forwardRef<Ref, ButtonProps>((props, ref) => {
const { type = "button", className, children, ...rest } = props;
const merged = clsx(
"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded",
className
);
return (
<button ref={ref} className={merged} {...rest}>
{children}
</button>
);
});
But our button's background color is not changing what is happening?
Well, it's due to CSS Precedence if you inspect the element in dev tools you can see the bg-red-500 class is applying. But also is bg-blue-500.
To fix this you need to extract the classes.
Extracting styles
We can use tailwind’s @apply directive to easily extract common utility patterns to CSS component classes.
We are going the use sass
so install the package sass
.
Then create a scss
file in the same directory as the component file.
// button.scss
.btn {
@apply bg-blue-500 hover:bg-blue-700 text-white font-bold;
@apply py-2 px-4;
@apply rounded;
}
Update our button's merged style:
// Button.tsx
const merged = clsx("btn", className);
After that we need to import this file to the global/index scss file.
@tailwind base;
@tailwind components;
// add your component styles from here
@import "../src/components/Button/button.scss";
// to here
@tailwind utilities;
Now we can override any default styles that our button component have
<Button className="bg-red-500 hover:bg-red-400 px-12 py-4 text-lg">
Disable
</Button>
Yay 🙌 you got a reusable button component. Let's spice it up by adding variants
Adding variants
Let's add an outline and ghost variant to our button component. Extend the button.scss
file to the following:
.btn {
@apply bg-blue-500 hover:bg-blue-700 text-white font-bold;
@apply py-2 px-4;
@apply rounded;
@apply transition-colors;
}
.btn-outline {
@apply border-2 border-blue-300 bg-opacity-0 text-blue-500;
&:hover,
&:focus {
@apply bg-opacity-10;
}
}
.btn-ghost {
@apply border-2 border-transparent bg-opacity-0 text-blue-500;
&:hover,
&:focus {
@apply border-blue-300;
}
}
Let's change our button component to take a variant
prop:
import clsx from "clsx";
import { forwardRef } from "react";
interface ButtonOptions {
/**
* Button display variants
* @default "solid"
* @type ButtonVariant
*/
variant?: ButtonVariant;
}
type Ref = HTMLButtonElement;
export type ButtonProps = React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> &
ButtonOptions;
type ButtonVariant = "outline" | "solid" | "ghost";
const getVariant = (variant: ButtonVariant) => {
switch (variant) {
case "outline":
return "btn-outline";
case "ghost":
return "btn-ghost";
default:
return undefined;
}
};
const Button = forwardRef<Ref, ButtonProps>((props, ref) => {
const {
variant = "solid",
type = "button",
className,
children,
...rest
} = props;
const merged = clsx("btn", getVariant(variant), className);
return (
<button ref={ref} className={merged} {...rest}>
{children}
</button>
);
});
Button.displayName = "Button";
export default Button;
Now you can use it like this:
<Button variant="ghost">Ghost</Button>
🎉 Woohoo, you just made a reusable button with custom props!
You can extend this to add more variants, sizing, and other props but in general, this is how we build reusable components with React and Tailwind CSS.
Top comments (1)
How do you intend to make this responsive without writing more code