Skip to content

Pricing Toggle Switch

A monthly/yearly billing switch with the price sliding to its new value.

Freeminimaleditorial
Category
Components
Added
2026-08-23
Deps
1
Updated
2026-09-01
Preview
MonthlyYearlySave 17%
$29/mo

Props

29
290
Requires framer-motion
"use client";

import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";

export function PricingToggleSwitch({
  monthlyPrice = 29,
  yearlyPrice = 290,
  currency = "$",
}: {
  monthlyPrice?: number;
  yearlyPrice?: number;
  currency?: string;
}) {
  const [yearly, setYearly] = useState(false);
  const price = yearly ? yearlyPrice : monthlyPrice;
  const savePct = Math.round((1 - yearlyPrice / (monthlyPrice * 12)) * 100);

  return (
    <div className="flex flex-col items-center gap-5 rounded-2xl border border-neutral-200 p-8">
      <div className="flex items-center gap-3">
        <span className={`text-sm font-medium ${!yearly ? "text-neutral-900" : "text-neutral-400"}`}>
          Monthly
        </span>
        <button
          role="switch"
          aria-checked={yearly}
          onClick={() => setYearly((v) => !v)}
          className={`h-5 w-9 rounded-full transition-colors ${yearly ? "bg-red-500" : "bg-neutral-200"} relative`}
        >
          <span
            className={`absolute top-0.5 left-0.5 h-4 w-4 rounded-full bg-white transition-transform ${
              yearly ? "translate-x-4" : "translate-x-0"
            }`}
          />
        </button>
        <span className={`text-sm font-medium ${yearly ? "text-neutral-900" : "text-neutral-400"}`}>
          Yearly
          {savePct > 0 && (
            <span className="ml-1.5 rounded-full bg-red-50 px-1.5 py-0.5 text-[10px] font-bold text-red-600">
              Save {savePct}%
            </span>
          )}
        </span>
      </div>

      <div className="relative flex h-14 items-start overflow-hidden">
        <AnimatePresence mode="popLayout">
          <motion.div
            key={yearly ? "yearly" : "monthly"}
            initial={{ y: 20, opacity: 0 }}
            animate={{ y: 0, opacity: 1 }}
            exit={{ y: -20, opacity: 0 }}
            transition={{ duration: 0.3, ease: [0.65, 0, 0.35, 1] }}
            className="flex items-start text-4xl font-black text-neutral-900"
          >
            {currency}
            {price}
            <span className="mt-2 ml-1 text-sm font-medium text-neutral-400">/{yearly ? "yr" : "mo"}</span>
          </motion.div>
        </AnimatePresence>
      </div>
    </div>
  );
}
Notes

About this component

Swapping the displayed price between monthly and yearly billing with a plain state update is instant but forgettable, especially if the two numbers happen to share a leading digit. This slides the outgoing price up and out while the new one slides up from below, using AnimatePresence in popLayout mode keyed on the billing period, the same vertical-swap pattern you would use for an odometer digit. The toggle switch itself is a real switch-role element with aria-checked wired to state, not a repurposed checkbox, which is worth keeping if you pull this into a form. The implementation detail worth copying carefully: the thumb has an explicit left-0.5 resting position rather than relying on default static positioning plus a translate-x utility when active. Skip the explicit left and the thumb's untoggled position depends on the track's own padding and sizing rather than being pinned, which looks fine in isolation and glitches the moment the track's size changes.

Curator’s note

The toggle track+thumb reuses the exact pattern fixed sitewide for the shared boolean control: an explicit left-0.5 resting position on the thumb, not an implicit static position plus translate — omitting the explicit left is what made the props-panel switch glitch outside its own track when activated.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

Pro

A live estimator with real marginal-tier overage pricing and an annual discount toggle.

pricingcalculatorslider
Pro

Pricing Page

templates

A pricing page that computes: seat slider, annual toggle and an expandable comparison matrix.

pricingsaasplans

A checkbox whose checkmark draws itself in as a real SVG path animation.

checkboxformsvg