How to Create a Pricing Table with a Monthly/Yearly Toggle in Tailwind CSS and Next.js
Tags: tutorial, nextjs, react, tailwindcss
Published: 2023-05-30 08:41:43

####**[Live Demo](https://cruip-tutorials-next.vercel.app/pricing-table) / [Download](https://github.com/cruip/cruip-tutorials/blob/main/particle-animation/particle-animation.js)**

Welcome to the second series of **How to Create a Pricing Table with a Monthly/Yearly Toggle Switch in Tailwind CSS**! In the [previous post](https://cruip.com/how-to-create-a-pricing-table-with-a-monthly-yearly-toggle-in-tailwind-css/), we covered the basics of building this essential component with **Tailwind CSS** and **Alpine.js**. Now, we’re taking it a step further and exploring how to build it in **Next.js**.

If you’re looking for some inspiration before diving into the tutorial, be sure to explore our collection of [Tailwind CSS templates](https://cruip.com/) featuring a pricing table component. For example, our [recruitment website template](https://cruip.com/talent/) called **Talent**, or our highly popular [SaaS website template](https://cruip.com/open-pro/) called **Open PRO**.

One last thing to note: Just like our previous article on [creating a Video Modal component](https://cruip.com/how-to-build-a-modal-video-component-with-tailwind-css-and-next-js/) in Next.js, we’ll jump right into building the component in this tutorial. However, If you prefer to skip the process of creating a Next.js app from scratch, feel free to fork our public repository. It includes the components we’ll be building, along with many others.

Ready? Let’s create a file called `pricing-table.tsx` in the `components` folder, and define a function for the component:

```tsx
  'use client'
  export default function PricingTable() {
    return (
      <div>
        {/* Pricing toggle */}
        {/* Pricing table */}
      </div>
    )
  }
```

Since the component requires client-side interactivity to allow users to switch between annual and monthly prices, we need the `'use client'` directive at the beginning of the file.

Within the `return` method we will include the HTML from our [previous article](https://cruip.com/how-to-create-a-pricing-table-with-a-monthly-yearly-toggle-in-tailwind-css/), using the JSX syntax, and replacing every instance of `class` with `className`. Lastly, we’ll remove any Alpine.js code that’s not necessary for building the component.

Using a state to show annual or monthly prices
----------------------------------------------

Our pricing table offers three different tiers, each with annual and monthly pricing options. The monthly price is lower for those who choose to pay annually.

So, let’s set a boolean state called `yearly`, which is true by default. When `yearly` is `true`, we will show the discounted prices. Conversely, when it is `false`, we will show the price for those who prefer to pay month by month.

```tsx
  'use client'
  import { useState } from 'react'
  export default function PricingTable() {
    const [yearly, setYearly] = useState<boolean>(true)
    return (
      <div>
        {/* Pricing toggle */}
        {/* Pricing table */}
      </div>
    )
  }

```

Creating a working pricing toggle
---------------------------------

Now let’s insert the pricing toggle into the component, making sure to make it functional. By functional, we mean that when the “Monthly” button is clicked, the `yearly` state becomes `false`, and when “Yearly” is clicked, the state becomes `true` again.

```tsx
  'use client'
  import { useState } from 'react'
  export default function PricingTable() {
    const [yearly, setYearly] = useState<boolean>(true)
    return (
      <div>
        {/* Pricing toggle */}
        <div className="flex justify-center max-w-[14rem] m-auto mb-8 lg:mb-16">
          <div className="relative flex w-full p-1 bg-white dark:bg-slate-900 rounded-full">
            <span className="absolute inset-0 m-1 pointer-events-none" aria-hidden="true">
              <span className={`absolute inset-0 w-1/2 bg-indigo-500 rounded-full shadow-sm shadow-indigo-950/10 transform transition-transform duration-150 ease-in-out ${yearly ? 'translate-x-0' : 'translate-x-full'}`}></span>
            </span>
            <button
              className={`relative flex-1 text-sm font-medium h-8 rounded-full focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 dark:focus-visible:ring-slate-600 transition-colors duration-150 ease-in-out ${yearly ? 'text-white' : 'text-slate-500 dark:text-slate-400'}`}
              onClick={() => setYearly(true)}
            >
              Yearly <span className={`${yearly ? 'text-indigo-200' : 'text-slate-400 dark:text-slate-500'}`}>-20%</span>
            </button>
            <button
              className={`relative flex-1 text-sm font-medium h-8 rounded-full focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 dark:focus-visible:ring-slate-600 transition-colors duration-150 ease-in-out ${yearly ? 'text-slate-500 dark:text-slate-400' : 'text-white'}`}
              onClick={() => setYearly(false)}
            >
              Monthly
            </button>
          </div>
        </div>
        {/* Pricing table */}
      </div>
    )
  }

```

As you may have noticed, we’re using the `onClick` event handler to change the yearly state.

Also, we’re also listening the current state to translate the element with the blue background and highlight the “active” button: `${yearly ? 'translate-x-0' : 'translate-x-full'}`.

Finally, we’re defining the colors of the buttons in relation to the current state, in this way: `${yearly ? 'text-white' : 'text-slate-500 dark:text-slate-400'}`.

Creating the pricing tabs with prices
-------------------------------------

We can now add the pricing tabs. To display different prices based on the “yearly” state, we have been using a ternary operator. For example:

```tsx

  <span className="text-slate-900 dark:text-slate-200 font-bold text-4xl">{yearly ? '29' : '35'}</span>

```

Here is the full code for the pricing tabs:

```tsx
  <div className="max-w-sm mx-auto grid gap-6 lg:grid-cols-3 items-start lg:max-w-none">
    {/* Pricing tab 1 */}
    <div className="h-full">
      <div className="relative flex flex-col h-full p-6 rounded-2xl bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-900 shadow shadow-slate-950/5">
        <div className="mb-5">
          <div className="text-slate-900 dark:text-slate-200 font-semibold mb-1">Essential</div>
          <div className="inline-flex items-baseline mb-2">
            <span className="text-slate-900 dark:text-slate-200 font-bold text-3xl">$</span>
            <span className="text-slate-900 dark:text-slate-200 font-bold text-4xl">{yearly ? '29' : '35'}</span>
            <span className="text-slate-500 font-medium">/mo</span>
          </div>
          <div className="text-sm text-slate-500 mb-5">There are many variations available, but the majority have suffered.</div>
          <a className="w-full inline-flex justify-center whitespace-nowrap rounded-lg bg-indigo-500 px-3.5 py-2.5 text-sm font-medium text-white shadow-sm shadow-indigo-950/10 hover:bg-indigo-600 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 dark:focus-visible:ring-slate-600 transition-colors duration-150" href="#0">
            Purchase Plan
          </a>
        </div>
        <div className="text-slate-900 dark:text-slate-200 font-medium mb-3">Includes:</div>
        <ul className="text-slate-600 dark:text-slate-400 text-sm space-y-3 grow">
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Unlimited placeholder texts</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Consectetur adipiscing elit</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Excepteur sint occaecat cupidatat</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Officia deserunt mollit anim</span>
          </li>
        </ul>
      </div>
    </div>
    {/* Pricing tab 2 */}
    <div className="h-full dark">
      <div className="relative flex flex-col h-full p-6 rounded-2xl bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-900 shadow shadow-slate-950/5">
        <div className="absolute top-0 right-0 mr-6 -mt-4">
          <div className="inline-flex items-center text-xs font-semibold py-1.5 px-3 bg-emerald-500 text-white rounded-full shadow-sm shadow-slate-950/5">Most Popular</div>
        </div>            
        <div className="mb-5">
          <div className="text-slate-900 dark:text-slate-200 font-semibold mb-1">Perform</div>
          <div className="inline-flex items-baseline mb-2">
            <span className="text-slate-900 dark:text-slate-200 font-bold text-3xl">$</span>
            <span className="text-slate-900 dark:text-slate-200 font-bold text-4xl">{yearly ? '49' : '55'}</span>
            <span className="text-slate-500 font-medium">/mo</span>
          </div>
          <div className="text-sm text-slate-500 mb-5">There are many variations available, but the majority have suffered.</div>
          <a className="w-full inline-flex justify-center whitespace-nowrap rounded-lg bg-indigo-500 px-3.5 py-2.5 text-sm font-medium text-white shadow-sm shadow-indigo-950/10 hover:bg-indigo-600 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 dark:focus-visible:ring-slate-600 transition-colors duration-150" href="#0">
            Purchase Plan
          </a>
        </div>
        <div className="text-slate-900 dark:text-slate-200 font-medium mb-3">Includes:</div>
        <ul className="text-slate-600 dark:text-slate-400 text-sm space-y-3 grow">
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Unlimited placeholder texts</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Consectetur adipiscing elit</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Excepteur sint occaecat cupidatat</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Officia deserunt mollit anim</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Predefined chunks as necessary</span>
          </li>
        </ul>
      </div>
    </div>
    {/* Pricing tab 3 */}
    <div className="h-full">
      <div className="relative flex flex-col h-full p-6 rounded-2xl bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-900 shadow shadow-slate-950/5">
        <div className="mb-5">
          <div className="text-slate-900 dark:text-slate-200 font-semibold mb-1">Enterprise</div>
          <div className="inline-flex items-baseline mb-2">
            <span className="text-slate-900 dark:text-slate-200 font-bold text-3xl">$</span>
            <span className="text-slate-900 dark:text-slate-200 font-bold text-4xl">{yearly ? '79' : '85'}</span>
            <span className="text-slate-500 font-medium">/mo</span>
          </div>
          <div className="text-sm text-slate-500 mb-5">There are many variations available, but the majority have suffered.</div>
          <a className="w-full inline-flex justify-center whitespace-nowrap rounded-lg bg-indigo-500 px-3.5 py-2.5 text-sm font-medium text-white shadow-sm shadow-indigo-950/10 hover:bg-indigo-600 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 dark:focus-visible:ring-slate-600 transition-colors duration-150" href="#0">
            Purchase Plan
          </a>
        </div>
        <div className="text-slate-900 dark:text-slate-200 font-medium mb-3">Includes:</div>
        <ul className="text-slate-600 dark:text-slate-400 text-sm space-y-3 grow">
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Unlimited placeholder texts</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Consectetur adipiscing elit</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Excepteur sint occaecat cupidatat</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Officia deserunt mollit anim</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Predefined chunks as necessary</span>
          </li>
          <li className="flex items-center">
            <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
              <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
            </svg>
            <span>Free from repetition</span>
          </li>              
        </ul>
      </div>
    </div>
  </div>

```

At this point in the tutorial, we have the option to either stop, or take a further step forward and make the code even more modular.

As you can see, the 3 pricing tabs that make up the table share the same structure and differ only in some small details, such as the name of the plan, the short description, the prices, the list of features, and whether a pricing tab is highlighted or not. Aside from these differences, the structure remains identical.

Given that the React framework aims to create reusable components and a concise codebase, we will now proceed to extract a **pricing tab component** that can receive the properties that need to change.

Create a reusable pricing tab component
---------------------------------------

Instead of generating a new file named `pricing-tab.tsx` to define the single pricing tab, we want to create a functional component within the same file as the parent pricing table component.

So, we will export the `PricingTable` function as the default, and define a simple `PricingTab` function within the same file.

The `PricingTab` function generates the markup of an individual pricing tab, and can accept props from the parent component.

```tsx
  interface PricingTabProps {
    yearly: boolean
    popular?: boolean
    planName: string
    price: {
      monthly: number
      yearly: number
    }
    planDescription: string
    features: string[]
  }
  function PricingTab(props: PricingTabProps) {
    return (
      <div className={`h-full ${props.popular ? 'dark' : ''}`}>
        <div className="relative flex flex-col h-full p-6 rounded-2xl bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-900 shadow shadow-slate-950/5">
          {props.popular && (
            <div className="absolute top-0 right-0 mr-6 -mt-4">
              <div className="inline-flex items-center text-xs font-semibold py-1.5 px-3 bg-emerald-500 text-white rounded-full shadow-sm shadow-slate-950/5">Most Popular</div>
            </div>
          )}
          <div className="mb-5">
            <div className="text-slate-900 dark:text-slate-200 font-semibold mb-1">{props.planName}</div>
            <div className="inline-flex items-baseline mb-2">
              <span className="text-slate-900 dark:text-slate-200 font-bold text-3xl">$</span>
              <span className="text-slate-900 dark:text-slate-200 font-bold text-4xl">{props.yearly ? props.price.yearly : props.price.monthly}</span>
              <span className="text-slate-500 font-medium">/mo</span>
            </div>
            <div className="text-sm text-slate-500 mb-5">{props.planDescription}</div>
            <a className="w-full inline-flex justify-center whitespace-nowrap rounded-lg bg-indigo-500 px-3.5 py-2.5 text-sm font-medium text-white shadow-sm shadow-indigo-950/10 hover:bg-indigo-600 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 dark:focus-visible:ring-slate-600 transition-colors duration-150" href="#0">
              Purchase Plan
            </a>
          </div>
          <div className="text-slate-900 dark:text-slate-200 font-medium mb-3">Includes:</div>
          <ul className="text-slate-600 dark:text-slate-400 text-sm space-y-3 grow">
            {props.features.map((feature, index) => {
              return (
                <li key={index} className="flex items-center">
                  <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
                    <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
                  </svg>
                  <span>{feature}</span>
                </li>
              )
            })}
          </ul>
        </div>
      </div>
    )
  }

```

Since we are using TypeScript, we have defined an `interface` for the props that the `PricingTab` component expects to receive when displaying a pricing tab.

We have also added an optional `popular` prop, that can be used to display the card in dark mode and label it as the “Most Popular” option.

Finally, we have used the `map()` method to iterate over the array of features passed in through the `props` object, and render each feature as a list item.

Cool! The `PricingTab` component is now ready and it can be used like this:

```tsx
  <PricingTab
    yearly={yearly}
    popular={true}
    planName="Perform"
    price={{ yearly: 49, monthly: 55 }}
    planDescription="There are many variations available, but the majority have suffered."
    features={[
      'Unlimited placeholder texts',
      'Consectetur adipiscing elit',
      'Excepteur sint occaecat cupidatat',
      'Officia deserunt mollit anim',
      'Predefined chunks as necessary',
    ]} />

```

Conclusions
-----------

Creating a pricing table component may seem harsh for those new to Next.js, but fear not! It’s much simpler than it appears. Trust me on this one. 😉 Without further ado, here’s the final code for your reference:

```tsx
  'use client'
  import { useState } from 'react'
  interface PricingTabProps {
    yearly: boolean
    popular?: boolean
    planName: string
    price: {
      monthly: number
      yearly: number
    }
    planDescription: string
    features: string[]
  }
  function PricingTab(props: PricingTabProps) {
    return (
      <div className={`h-full ${props.popular ? 'dark' : ''}`}>
        <div className="relative flex flex-col h-full p-6 rounded-2xl bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-900 shadow shadow-slate-950/5">
          {props.popular && (
            <div className="absolute top-0 right-0 mr-6 -mt-4">
              <div className="inline-flex items-center text-xs font-semibold py-1.5 px-3 bg-emerald-500 text-white rounded-full shadow-sm shadow-slate-950/5">Most Popular</div>
            </div>
          )}
          <div className="mb-5">
            <div className="text-slate-900 dark:text-slate-200 font-semibold mb-1">{props.planName}</div>
            <div className="inline-flex items-baseline mb-2">
              <span className="text-slate-900 dark:text-slate-200 font-bold text-3xl">$</span>
              <span className="text-slate-900 dark:text-slate-200 font-bold text-4xl">{props.yearly ? props.price.yearly : props.price.monthly}</span>
              <span className="text-slate-500 font-medium">/mo</span>
            </div>
            <div className="text-sm text-slate-500 mb-5">{props.planDescription}</div>
            <a className="w-full inline-flex justify-center whitespace-nowrap rounded-lg bg-indigo-500 px-3.5 py-2.5 text-sm font-medium text-white shadow-sm shadow-indigo-950/10 hover:bg-indigo-600 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 dark:focus-visible:ring-slate-600 transition-colors duration-150" href="#0">
              Purchase Plan
            </a>
          </div>
          <div className="text-slate-900 dark:text-slate-200 font-medium mb-3">Includes:</div>
          <ul className="text-slate-600 dark:text-slate-400 text-sm space-y-3 grow">
            {props.features.map((feature, index) => {
              return (
                <li key={index} className="flex items-center">
                  <svg className="w-3 h-3 fill-emerald-500 mr-3 shrink-0" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
                    <path d="M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" />
                  </svg>
                  <span>{feature}</span>
                </li>
              )
            })}
          </ul>
        </div>
      </div>
    )
  }
  export default function PricingTable() {
    const [yearly, setYearly] = useState<boolean>(true)
    return (
      <div>
        {/* Pricing toggle */}
        <div className="flex justify-center max-w-[14rem] m-auto mb-8 lg:mb-16">
          <div className="relative flex w-full p-1 bg-white dark:bg-slate-900 rounded-full">
            <span className="absolute inset-0 m-1 pointer-events-none" aria-hidden="true">
              <span className={`absolute inset-0 w-1/2 bg-indigo-500 rounded-full shadow-sm shadow-indigo-950/10 transform transition-transform duration-150 ease-in-out ${yearly ? 'translate-x-0' : 'translate-x-full'}`}></span>
            </span>
            <button
              className={`relative flex-1 text-sm font-medium h-8 rounded-full focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 dark:focus-visible:ring-slate-600 transition-colors duration-150 ease-in-out ${yearly ? 'text-white' : 'text-slate-500 dark:text-slate-400'}`}
              onClick={() => setYearly(true)}
            >
              Yearly <span className={`${yearly ? 'text-indigo-200' : 'text-slate-400 dark:text-slate-500'}`}>-20%</span>
            </button>
            <button
              className={`relative flex-1 text-sm font-medium h-8 rounded-full focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 dark:focus-visible:ring-slate-600 transition-colors duration-150 ease-in-out ${yearly ? 'text-slate-500 dark:text-slate-400' : 'text-white'}`}
              onClick={() => setYearly(false)}
            >
              Monthly
            </button>
          </div>
        </div>
        <div className="max-w-sm mx-auto grid gap-6 lg:grid-cols-3 items-start lg:max-w-none">
          {/* Pricing tab 1 */}
          <PricingTab
            yearly={yearly}
            planName="Essential"
            price={{ yearly: 29, monthly: 35 }}
            planDescription="There are many variations available, but the majority have suffered."
            features={[
              'Unlimited placeholder texts',
              'Consectetur adipiscing elit',
              'Excepteur sint occaecat cupidatat',
              'Officia deserunt mollit anim',
            ]} />
          {/* Pricing tab 2 */}
          <PricingTab
            yearly={yearly}
            popular={true}
            planName="Perform"
            price={{ yearly: 49, monthly: 55 }}
            planDescription="There are many variations available, but the majority have suffered."
            features={[
              'Unlimited placeholder texts',
              'Consectetur adipiscing elit',
              'Excepteur sint occaecat cupidatat',
              'Officia deserunt mollit anim',
              'Predefined chunks as necessary',
            ]} />
          {/* Pricing tab 3 */}
          <PricingTab
            yearly={yearly}
            planName="Enterprise"
            price={{ yearly: 79, monthly: 85 }}
            planDescription="There are many variations available, but the majority have suffered."
            features={[
              'Unlimited placeholder texts',
              'Consectetur adipiscing elit',
              'Excepteur sint occaecat cupidatat',
              'Officia deserunt mollit anim',
              'Predefined chunks as necessary',
              'Free from repetition',
            ]} />
        </div>
      </div>
    )
  }

```

Now that you’re equipped to implement the pricing table component into your app or website and start attracting valuable customers, I want to remind you that this tutorial is not limited to Next.js alone, but is also available in HTML + Alpine.js and Vue: