Skip to main content
Back to Blog
Tutorials
4 min read
November 30, 2024

How to Build a Pricing Page Component in React

Build a responsive pricing page with toggle for monthly/annual billing, feature comparison, and highlighted plans using React and Tailwind CSS.

Ryel Banfield

Founder & Lead Developer

A well-designed pricing page is one of the most important pages on a SaaS or service website. Here is how to build one with React and Tailwind CSS.

Step 1: Define Pricing Data

// lib/pricing.ts
export type PricingPlan = {
  name: string;
  description: string;
  monthlyPrice: number;
  annualPrice: number;
  features: string[];
  highlighted: boolean;
  cta: string;
  href: string;
};

export const plans: PricingPlan[] = [
  {
    name: "Starter",
    description: "For individuals and small projects",
    monthlyPrice: 29,
    annualPrice: 290,
    features: [
      "5 pages",
      "Basic SEO setup",
      "Contact form",
      "Mobile responsive",
      "1 revision round",
    ],
    highlighted: false,
    cta: "Get Started",
    href: "/contact?plan=starter",
  },
  {
    name: "Professional",
    description: "For growing businesses",
    monthlyPrice: 79,
    annualPrice: 790,
    features: [
      "15 pages",
      "Advanced SEO",
      "CMS integration",
      "Analytics setup",
      "E-commerce ready",
      "3 revision rounds",
      "Priority support",
    ],
    highlighted: true,
    cta: "Get Started",
    href: "/contact?plan=professional",
  },
  {
    name: "Enterprise",
    description: "For large organizations",
    monthlyPrice: 199,
    annualPrice: 1990,
    features: [
      "Unlimited pages",
      "Custom functionality",
      "API integrations",
      "Performance optimization",
      "Security hardening",
      "Unlimited revisions",
      "Dedicated support",
      "SLA guarantee",
    ],
    highlighted: false,
    cta: "Contact Sales",
    href: "/contact?plan=enterprise",
  },
];

Step 2: Build the Billing Toggle

"use client";

import { useState } from "react";

function BillingToggle({
  isAnnual,
  onToggle,
}: {
  isAnnual: boolean;
  onToggle: () => void;
}) {
  return (
    <div className="flex items-center justify-center gap-3">
      <span
        className={`text-sm font-medium ${!isAnnual ? "text-gray-900 dark:text-white" : "text-gray-500"}`}
      >
        Monthly
      </span>
      <button
        onClick={onToggle}
        className="relative inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition-colors dark:bg-gray-700"
        role="switch"
        aria-checked={isAnnual}
      >
        <span
          className={`inline-block h-4 w-4 rounded-full bg-white transition-transform ${
            isAnnual ? "translate-x-6" : "translate-x-1"
          }`}
        />
      </button>
      <span
        className={`text-sm font-medium ${isAnnual ? "text-gray-900 dark:text-white" : "text-gray-500"}`}
      >
        Annual
        <span className="ml-1 text-xs text-green-600 font-semibold">
          Save 17%
        </span>
      </span>
    </div>
  );
}

Step 3: Build the Pricing Card

function PricingCard({
  plan,
  isAnnual,
}: {
  plan: PricingPlan;
  isAnnual: boolean;
}) {
  const price = isAnnual ? plan.annualPrice : plan.monthlyPrice;
  const period = isAnnual ? "/year" : "/month";

  return (
    <div
      className={`relative rounded-2xl p-8 ${
        plan.highlighted
          ? "border-2 border-blue-600 bg-white shadow-xl dark:bg-gray-800"
          : "border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
      }`}
    >
      {plan.highlighted && (
        <div className="absolute -top-4 left-1/2 -translate-x-1/2">
          <span className="rounded-full bg-blue-600 px-4 py-1 text-sm font-medium text-white">
            Most Popular
          </span>
        </div>
      )}

      <div className="mb-6">
        <h3 className="text-xl font-bold text-gray-900 dark:text-white">
          {plan.name}
        </h3>
        <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
          {plan.description}
        </p>
      </div>

      <div className="mb-6">
        <span className="text-4xl font-bold text-gray-900 dark:text-white">
          ${price}
        </span>
        <span className="text-gray-500 dark:text-gray-400">{period}</span>
      </div>

      <a
        href={plan.href}
        className={`block w-full rounded-lg py-3 text-center font-medium transition-colors ${
          plan.highlighted
            ? "bg-blue-600 text-white hover:bg-blue-700"
            : "bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600"
        }`}
      >
        {plan.cta}
      </a>

      <ul className="mt-8 space-y-3">
        {plan.features.map((feature) => (
          <li key={feature} className="flex items-start gap-3">
            <svg
              className="h-5 w-5 flex-shrink-0 text-green-500"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
              strokeWidth={2}
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                d="M5 13l4 4L19 7"
              />
            </svg>
            <span className="text-sm text-gray-600 dark:text-gray-300">
              {feature}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

Step 4: Assemble the Pricing Page

"use client";

import { useState } from "react";
import { plans } from "@/lib/pricing";

export default function PricingPage() {
  const [isAnnual, setIsAnnual] = useState(false);

  return (
    <section className="py-20">
      <div className="mx-auto max-w-7xl px-6">
        <div className="text-center">
          <h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-5xl">
            Simple, transparent pricing
          </h1>
          <p className="mx-auto mt-4 max-w-2xl text-lg text-gray-500 dark:text-gray-400">
            Choose the plan that fits your business. No hidden fees. Cancel anytime.
          </p>
        </div>

        <div className="mt-10">
          <BillingToggle
            isAnnual={isAnnual}
            onToggle={() => setIsAnnual(!isAnnual)}
          />
        </div>

        <div className="mt-12 grid gap-8 lg:grid-cols-3">
          {plans.map((plan) => (
            <PricingCard key={plan.name} plan={plan} isAnnual={isAnnual} />
          ))}
        </div>

        <p className="mt-12 text-center text-sm text-gray-500">
          All plans include SSL, hosting, and 30-day money-back guarantee.
        </p>
      </div>
    </section>
  );
}

Step 5: Add a Feature Comparison Table

const features = [
  { name: "Custom domain", starter: true, professional: true, enterprise: true },
  { name: "SSL certificate", starter: true, professional: true, enterprise: true },
  { name: "SEO optimization", starter: "Basic", professional: "Advanced", enterprise: "Advanced" },
  { name: "CMS", starter: false, professional: true, enterprise: true },
  { name: "E-commerce", starter: false, professional: true, enterprise: true },
  { name: "API integrations", starter: false, professional: false, enterprise: true },
  { name: "Priority support", starter: false, professional: true, enterprise: true },
  { name: "Custom features", starter: false, professional: false, enterprise: true },
];

function ComparisonTable() {
  return (
    <div className="mt-20 overflow-x-auto">
      <table className="w-full text-left">
        <thead>
          <tr className="border-b dark:border-gray-700">
            <th className="py-4 pr-6 text-sm font-medium text-gray-500">Feature</th>
            <th className="px-6 py-4 text-sm font-medium text-gray-500">Starter</th>
            <th className="px-6 py-4 text-sm font-medium text-gray-500">Professional</th>
            <th className="px-6 py-4 text-sm font-medium text-gray-500">Enterprise</th>
          </tr>
        </thead>
        <tbody>
          {features.map((feature) => (
            <tr key={feature.name} className="border-b dark:border-gray-700">
              <td className="py-4 pr-6 text-sm text-gray-900 dark:text-white">
                {feature.name}
              </td>
              {[feature.starter, feature.professional, feature.enterprise].map(
                (value, i) => (
                  <td key={i} className="px-6 py-4 text-sm">
                    {typeof value === "boolean" ? (
                      value ? (
                        <span className="text-green-500">Yes</span>
                      ) : (
                        <span className="text-gray-300">—</span>
                      )
                    ) : (
                      <span className="text-gray-600 dark:text-gray-300">{value}</span>
                    )}
                  </td>
                )
              )}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Accessibility Considerations

  • Use role="switch" and aria-checked on the billing toggle
  • Ensure sufficient color contrast for highlighted plans
  • Make the comparison table scrollable on mobile
  • Include clear focus styles on interactive elements

Need a Custom Pricing Page?

We design and build conversion-optimized pricing pages for SaaS and service businesses. Contact us to discuss your project.

Reactpricing pagecomponentsTailwind CSStutorial

Ready to Start Your Project?

RCB Software builds world-class websites and applications for businesses worldwide.

Get in Touch

Related Articles