DEV Community

mhcrocky
mhcrocky

Posted on

Creating a reusable Button component with React and Tailwind CSS

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Include Tailwind in your CSS
You need to add these 3 lines to your global/index CSS file.

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Awesome now we can use our button like this

// import Button from "../src/components/Button/Button";
<Button>Anything</Button>
Enter fullscreen mode Exit fullscreen mode

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 clsxto 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>
  );
});
Enter fullscreen mode Exit fullscreen mode

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.

Image description

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 sassso install the package sass.
Then create a scssfile 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;


Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Let's change our button component to take a variantprop:


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;

Enter fullscreen mode Exit fullscreen mode

Now you can use it like this:

<Button variant="ghost">Ghost</Button>

Enter fullscreen mode Exit fullscreen mode

๐ŸŽ‰ 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)

Collapse
 
codewithjohnson profile image
codewithjohnson

How do you intend to make this responsive without writing more code