Skip to content
All Taste components

Pricing

Pricing — monthly/yearly toggle, plan rows

Plans as full-width rows you can scan top to bottom, with a monthly or yearly switch beside the heading.

monthly / yearly toggle · plan rows · recommended marker · stacks on mobile

Plans for every stage

Switch to yearly billing and keep two months for free.

  • Solo

    One person, a handful of clients

    • Unlimited invoices
    • Reminders
    • 1 user
    $9$7per monthper month, billed yearly
    Choose Solo
  • Team

    Most chosen

    A studio or small agency

    • Everything in Solo
    • Recurring billing
    • Client portal
    • 5 users
    $24$19per monthper month, billed yearly
    Choose Team
  • Company

    Finance team plus approvals

    • Everything in Team
    • Approvals
    • Accounting sync
    • SSO
    $59$47per monthper month, billed yearly
    Choose Company

Prices exclude sales tax. Need more than 50 users? Talk to us.

"use client";
import { useState } from "react";
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";

type Period = "monthly" | "yearly";

const plans = [
  { name: "Solo", note: "One person, a handful of clients", price: { monthly: "$9", yearly: "$7" }, cta: "Choose Solo", href: "/signup?plan=solo",
    features: ["Unlimited invoices", "Reminders", "1 user"] },
  { name: "Team", note: "A studio or small agency", price: { monthly: "$24", yearly: "$19" }, cta: "Choose Team", href: "/signup?plan=team", recommended: true,
    features: ["Everything in Solo", "Recurring billing", "Client portal", "5 users"] },
  { name: "Company", note: "Finance team plus approvals", price: { monthly: "$59", yearly: "$47" }, cta: "Choose Company", href: "/signup?plan=company",
    features: ["Everything in Team", "Approvals", "Accounting sync", "SSO"] },
];

export function PricingToggleRows() {
  const [period, setPeriod] = useState<Period>("monthly");
  return (
    <section aria-labelledby="pricing-rows-title" className="bg-bg px-5 py-16 font-sans sm:px-10 sm:py-20">
      <div className="mx-auto max-w-6xl">
        <div className="flex flex-wrap items-end justify-between gap-x-8 gap-y-5">
          <div className="max-w-xl">
            <h2 id="pricing-rows-title" className="text-3xl font-semibold tracking-tight text-fg sm:text-4xl">Plans for every stage</h2>
            <p className="mt-4 text-base text-fg-muted sm:text-lg">Switch to yearly billing and keep two months for free.</p>
          </div>
          <div role="group" aria-label="Billing period" className="flex gap-0.5 rounded-lg bg-surface-2 p-[3px]">
            {(["monthly", "yearly"] as const).map((p) => (
              <button
                key={p}
                type="button"
                aria-pressed={period === p}
                onClick={() => setPeriod(p)}
                className="inline-flex h-8 items-center gap-1.5 rounded px-3.5 text-[13px] font-medium capitalize text-fg-muted transition aria-pressed:bg-bg aria-pressed:text-fg aria-pressed:shadow-sm"
              >
                {p}
                {p === "yearly" && <span className="rounded-full bg-primary-soft px-1.5 text-[11px] text-primary-soft-fg">−20%</span>}
              </button>
            ))}
          </div>
        </div>

        <ul className="mt-10 border-t border-border-strong">
          {plans.map((plan) => (
            <li
              key={plan.name}
              className={cn(
                "relative grid gap-4 border-b border-border py-7 lg:grid-cols-[1fr_1.5fr_0.8fr_168px] lg:items-center lg:gap-8",
                plan.recommended && "before:absolute before:-top-px before:left-0 before:h-0.5 before:w-16 before:bg-primary",
              )}
            >
              <div>
                <div className="flex items-center gap-2">
                  <h3 className="text-lg font-semibold text-fg">{plan.name}</h3>
                  {plan.recommended && <span className="rounded-full bg-primary-soft px-2 py-0.5 text-xs font-medium text-primary-soft-fg">Most chosen</span>}
                </div>
                <p className="mt-1 text-[13px] text-fg-subtle">{plan.note}</p>
              </div>
              <ul className="flex flex-wrap gap-x-4 gap-y-2 text-[13.5px] text-fg-muted">
                {plan.features.map((f) => (
                  <li key={f} className="inline-flex items-center gap-1.5"><Check className="size-3.5 text-primary" aria-hidden />{f}</li>
                ))}
              </ul>
              <p className="flex flex-wrap items-baseline gap-x-2">
                <span className="text-4xl font-semibold tracking-tight tabular-nums text-fg">{plan.price[period]}</span>
                <span className="text-[13px] text-fg-subtle">{period === "yearly" ? "per month, billed yearly" : "per month"}</span>
              </p>
              <a
                href={plan.href}
                className={cn(
                  "inline-flex h-9 items-center justify-center rounded px-4 text-sm font-medium transition",
                  plan.recommended ? "bg-primary text-primary-fg hover:bg-primary-hover" : "border border-border-strong text-fg hover:bg-surface",
                )}
              >
                {plan.cta}
              </a>
            </li>
          ))}
        </ul>
        <p className="mt-5 text-[13px] text-fg-subtle">
          Prices exclude sales tax. Need more than 50 users? <a href="/contact" className="text-primary underline underline-offset-4">Talk to us</a>.
        </p>
      </div>
    </section>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.