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";// variantconst 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",},});// propsinterface ButtonPropsextends React.ButtonHTMLAttributes<HTMLButtonElement>,VariantProps<typeof buttonVariants> {}// componentexport const MyButton: React.FC<ButtonProps> = ({className,intent,size,...props}) => (<buttonclassName={twMerge(buttonVariants({ intent, size, className }))}{...props}></button>);
Then we can use and over-write the style as below example
<MyButtonsize={"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 ButtonPropsextends 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=""></label><inputid="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><inputid="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><buttonclassName={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";