DEV Community

Cover image for Button Component with CVA and Tailwind
Shubham Tiwari
Shubham Tiwari

Posted on

Button Component with CVA and Tailwind

Hello all Web developers and engineers, today i will be discussing about the CVA(class variance authority) and how we can use it to create reusable components in React/Next JS with multiple variants and stylings.

Existing problem

Suppose you are creating a button component for your website created with React/Next JS. Let's say you have 9 different types of buttons and each button has some addition stylings like border, gradients, border roundness, etc. In this scenario, we could pass the css class names having those styles as a prop to the component and render the button styles based on it or could use an object with key value pairs to map the button stylings based on the prop passed for the button styles. But it would not be readable, easy to manage and better code practices. To solve this type of issue, we have the CVA library.

How CVA helps us to solve the above issue?

CVA allows to create configuration for the styles in a JSON format where we can pass our styles like variants with multiple combinations. We can use naming conventions for those variants like primary, secondary, tertiary. The best part of this library is that it allows us to pass the variants as a prop. For example, if we have created 3 button variants, let's say primary, secondary, tertiary and named this variant property as "flavour", then we could pass this flavour with the value from those 3 values as a prop to the button component. It will be more readable, easy to manage and follows good coding practices.

Code example -


import { cva } from "class-variance-authority";
Enter fullscreen mode Exit fullscreen mode

Typescript types (For typescript based components

// intent will be used as a prop which will only accepts these // values
type intent = "primary" | "warning" | "danger" | "inverse" | "success" | "purple" | "default";

type ButtonVariantsProps = {
  intent?: Intent;
// could pass types for other things as variants like size, roundness, border

// Combining the button variant props along with other props
// the button component will accept like children, custom 
// class names and rest of the props.
type ButtonProps = ButtonVariantsProps & {
  customClassNames?: string;
  children: React.ReactNode;
  [key: string]: any;

// This will be used to check the types we will pass for the 
// configuration created by cva() method
type ButtonVariantsFunction = (props: ButtonVariantsProps) => string;
Enter fullscreen mode Exit fullscreen mode

Styling configuration with variants

Here we are using tailwind css classess to styles our button variants, you could use your custom css classes as well.

const ButtonVariants: ButtonVariantsFunction = cva(
  /* button base style, default for all variants */
  "transition-colors duration-150 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none",
  variants: {
     intent: {
          "bg-blue-600 hover:bg-blue-700 text-blue-100 hover:text-white",
          "bg-yellow-500 hover:bg-yellow-600 text-orange-100 hover:text-white",
          "bg-red-500 hover:bg-red-600 text-orange-100 hover:text-white",
          "bg-gray-600 hover:bg-gray-700 text-blue-100 hover:text-white",
          "bg-green-600 hover:bg-green-700 text-teal-100 hover:text-white",
          "bg-indigo-700 hover:bg-indigo-800 text-white",
          "bg-gray-500 hover:bg-gray-600",

   defaultVariants: {
      intent: "default", // will be used if we won't pass any prop for intent
Enter fullscreen mode Exit fullscreen mode

Exporting Button Component

export default function Button({
  customClassNames = "", 
}: ButtonProps): JSX.Element {
  return (
    <button className={`${ButtonVariants({ intent})} ${customClassNames}`} {...props}>{children}</button>
Enter fullscreen mode Exit fullscreen mode
  • We are calling our ButtonVariants method here and passing an object inside it with variant property "intent" we have created in configuration, along with it we have also passed customClassNames, which we can pass as a prop if we want and finally using the spread operator, we have spread the rest of the props.

Component Usage

"use client"
import React from "react";
import Button from "@/components/Button"; // update this according to your project structure

const ButtonRender = () => {
  return (
        <h1 className="text-center text-xl mb-10">Button variants</h1>

        <div className="mb-12">
          <h2 className="text-center mb-8">Flavours</h2>
          <div className="flex flex-wrap gap-6 justify-center items-center">
            <Button intent="primary" onClick={() => console.log("Primary")}>Primary</Button>
            <Button intent="success">Success</Button>
            <Button intent="warning">Warning</Button>
            <Button intent="danger">Danger</Button>
            <Button intent="inverse">Inverse</Button>
            <Button intent="purple">Purple</Button>
Enter fullscreen mode Exit fullscreen mode

Now we have 7 different variants of button to use, also you can see we are passing the intent as a prop to our button component and also passing onClick event handler, which will be added in rest of the props

Live preview of the button variants - Preview

Feel free to give suggestions in comments

You can contact me on -
Instagram -
LinkedIn -
Email -

You can help me with some donation at the link below Thank you👇👇
☕ --> <--

Also check these posts as well

Top comments (0)