DEV Community

Cover image for How to Create a Pricing Table with a Monthly/Yearly Toggle in Tailwind CSS and Next.js
Cruip
Cruip

Posted on • Edited on • Originally published at cruip.com

How to Create a Pricing Table with a Monthly/Yearly Toggle in Tailwind CSS and Next.js

Live Demo / Download

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, 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 featuring a pricing table component. For example, our recruitment website template called Talent, or our highly popular SaaS website template called Open PRO.

One last thing to note: Just like our previous article on creating a Video Modal component 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:

  'use client'
  export default function PricingTable() {
    return (
      <div>
        {/* Pricing toggle */}
        {/* Pricing table */}
      </div>
    )
  }
Enter fullscreen mode Exit fullscreen mode

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, 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.

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

Enter fullscreen mode Exit fullscreen mode

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.

  '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>
    )
  }

Enter fullscreen mode Exit fullscreen mode

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:


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

Enter fullscreen mode Exit fullscreen mode

Here is the full code for the pricing tabs:

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

Enter fullscreen mode Exit fullscreen mode

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.

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

Enter fullscreen mode Exit fullscreen mode

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:

  <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',
    ]} />

Enter fullscreen mode Exit fullscreen mode

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:

  '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>
    )
  }

Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)