Introduction#

GitHub this note shows how to create simple re-usable react components using cva, tailwind merge and clsx

Setup Project#

Let create a new nextjs project

npx create-next-app@latest

Then install cva, tailwind merge and clsx

npm install class-variance-authority tailwind-merge clsx

Project structure

|--app
|--global.css
|--layout.tsx
|--page.tsx
|--components
|--Badge
|--Badge.module.css
|--Badge.tsx
|--index.ts
|--Button
|--Button.tsx
|--index.tsx
|--Login
|--Login.tsx
|--index.tsx
|--next.config.ts
|--tsconfig.json
|--package.json
|--README.md

Reuseable Button#

Now let create a re-usable button component. There are three steps

  • button variants
  • button props
  • button component
import { VariantProps, cva } from "class-variance-authority";
import { twMerge } from "tailwind-merge";
// variant
const buttonVariants = cva("button", {
variants: {
intent: {
primary: [
"bg-blue-500",
"text-white",
"border-transparent",
"hover:bg-blue-600",
],
secondary: [
"bg-white",
"text-gray-800",
"border-gray-400",
"hover:bg-gray-100",
],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
},
compoundVariants: [{ intent: "primary", size: "medium", class: "uppercase" }],
defaultVariants: {
intent: "primary",
size: "medium",
},
});
// props
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
// component
export const MyButton: React.FC<ButtonProps> = ({
className,
intent,
size,
...props
}) => (
<button
className={twMerge(buttonVariants({ intent, size, className }))}
{...props}
></button>
);

Then we can use and over-write the style as below example

<MyButton
size={"medium"}
intent={"secondary"}
className="rounded-md bg-green-500 px-11"
>
Order
</MyButton>

Login Component#

Let create a simple Login component

  • Conditional style using clsx
  • React forward ref to create a component
"use client";
import React, { useRef, useState } from "react";
import clsx from "clsx";
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
export const MyLoginForm = React.forwardRef<HTMLButtonElement, ButtonProps>(
() => {
const [count, setCount] = useState<number>(0);
const ref = useRef<HTMLInputElement>(null);
const handleClick = () => {
setCount(count + 1);
console.log("click me");
if (ref.current) {
ref.current.focus();
}
};
return (
<div className="bg-gray-200 shadow-lg p-10 max-w-sm w-full flex flex-col gap-5">
<label htmlFor="email" className="">
Email
</label>
<input
id="email"
ref={ref}
placeholder="email"
className="block w-full border-2 border-gray-500 rounded-md py-3 px-5 outline-none focus:border-2 focus:border-green-500"
></input>
<label htmlFor="pass" className="">
Password
</label>
<input
id="pass"
ref={ref}
placeholder="password"
className="block w-full border-2 border-gray-500 rounded-md py-3 px-5 outline-none focus:border-2 focus:border-green-500"
></input>
<button
className={clsx({
"px-10 py-3 bg-green-500 rounded-md w-full mt-5": true,
"bg-orange-500": count % 2 == 0,
})}
onClick={handleClick}
>
Log In
</button>
</div>
);
}
);
MyLoginForm.displayName = "MyLoginForm";

Reference#