Get CodeArticle

Animated Pricing Toggle (Monthly/Yearly) — Free HTML, CSS & JS

Build a responsive animated Monthly/Yearly pricing toggle with sliding switch, morphing prices, and a Save badge. Free HTML, CSS, JavaScript, live demo, and screenshots.

TMTalal MehmoodFounder & CEO
7 min read
Featured image for Animated Pricing Toggle (Monthly/Yearly) — Free HTML, CSS & JS
Cover · Get Code

Quick answer:

An animated pricing toggle lets visitors switch Monthly ↔ Yearly with a sliding pill, morphing prices, a “Save %” badge, and yearly billed totals — all in pure HTML, CSS, and a small JavaScript snippet. Copy the code below or open the live demo first.

Live demo: Animated Pricing Toggle (Monthly/Yearly)

What you will build

This free Get Code pack includes:

  • A pill-style Monthly / Yearly switch with a sliding amber thumb
  • Price numbers that animate when billing changes
  • A teal Save 20% badge that appears on yearly
  • “Billed $X / year” helper text under each plan
  • Three responsive pricing cards (Starter, Growth, Studio)
  • Accessible role="switch" toggle with keyboard focus styles

No React. No jQuery. Drop it into WordPress, Elementor HTML, Shopify, or any static page.

Live preview screenshots

Monthly billing

Animated pricing toggle set to Monthly with three plan cards

Monthly is the default. Growth stays featured with the amber border and primary CTA.

Yearly billing + Save badge

Animated pricing toggle set to Yearly with Save 20 percent badge and updated prices

Flip the switch: prices drop, the save badge fades in, and yearly billed totals appear under each price.

Mobile layout

Mobile stacked pricing cards with yearly toggle active

On small screens cards stack to one column and the Popular plan rises to the top for better conversion focus.

How to use this code

  1. Create index.html (or paste into an HTML widget).
  2. Add the CSS in a <style> tag or styles.css.
  3. Add the JavaScript before </body>.
  4. Set each card’s prices with data-monthly and data-yearly.
  5. Open the page, click the toggle, confirm prices and the save badge update.
  6. Swap plan names, features, and CTAs for your offer.

Tip:

Keep yearly prices as the per-month equivalent (example: $79/mo → $63/mo billed annually). The script multiplies by 12 for the “Billed $X / year” line.

HTML code

html
<div class="billing">
  <span class="billing-label is-active" id="labelMonthly">Monthly</span>

  <button
    class="toggle"
    id="billingToggle"
    type="button"
    role="switch"
    aria-checked="false"
    data-billing="monthly"
    aria-label="Toggle yearly billing"
  >
    <span class="toggle-thumb" aria-hidden="true"></span>
    <span class="toggle-option" data-value="monthly">Monthly</span>
    <span class="toggle-option" data-value="yearly">Yearly</span>
  </button>

  <span class="billing-label" id="labelYearly">Yearly</span>
  <span class="save-badge" id="saveBadge" aria-live="polite">Save 20%</span>
</div>

<div class="cards">
  <article class="card" data-monthly="29" data-yearly="23">
    <div class="card-top"><h2 class="plan-name">Starter</h2></div>
    <div class="price-row">
      <span class="currency">$</span>
      <span class="price" data-price>29</span>
      <span class="period">/mo</span>
    </div>
    <p class="billed" data-billed>Billed $276 / year</p>
    <p class="desc">For freelancers launching a clean marketing site.</p>
    <ul class="features">
      <li>1 marketing site</li>
      <li>Basic SEO setup</li>
      <li>Email support</li>
    </ul>
    <a class="cta" href="#get-started">Get Starter</a>
  </article>

  <article class="card is-featured" data-monthly="79" data-yearly="63">
    <div class="card-top">
      <h2 class="plan-name">Growth</h2>
      <span class="plan-tag">Popular</span>
    </div>
    <div class="price-row">
      <span class="currency">$</span>
      <span class="price" data-price>79</span>
      <span class="period">/mo</span>
    </div>
    <p class="billed" data-billed>Billed $756 / year</p>
    <p class="desc">For brands that need conversion-focused pages.</p>
    <ul class="features">
      <li>Up to 5 pages</li>
      <li>CRO landing templates</li>
      <li>Priority support</li>
    </ul>
    <a class="cta is-primary" href="#get-started">Get Growth</a>
  </article>

  <article class="card" data-monthly="149" data-yearly="119">
    <div class="card-top"><h2 class="plan-name">Studio</h2></div>
    <div class="price-row">
      <span class="currency">$</span>
      <span class="price" data-price>149</span>
      <span class="period">/mo</span>
    </div>
    <p class="billed" data-billed>Billed $1,428 / year</p>
    <p class="desc">For teams shipping continuous design + build.</p>
    <ul class="features">
      <li>Unlimited requests queue</li>
      <li>Dedicated designer</li>
      <li>Shopify / WordPress</li>
    </ul>
    <a class="cta" href="#get-started">Get Studio</a>
  </article>
</div>

CSS code

css
:root {
  --ink: #0c0f14;
  --border: rgba(255, 255, 255, 0.12);
  --text: #f4f1ea;
  --muted: rgba(244, 241, 234, 0.62);
  --amber: #f9b818;
  --coral: #ff6b4a;
  --teal: #2dd4bf;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

.billing {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.85rem;
  flex-wrap: wrap;
  margin-bottom: 2.5rem;
}

.billing-label {
  font-weight: 600;
  color: var(--muted);
}
.billing-label.is-active { color: var(--text); }

.toggle {
  position: relative;
  display: inline-grid;
  grid-template-columns: 1fr 1fr;
  width: 220px;
  height: 48px;
  padding: 4px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.05);
  cursor: pointer;
}

.toggle-thumb {
  position: absolute;
  top: 4px;
  left: 4px;
  width: calc(50% - 4px);
  height: calc(100% - 8px);
  border-radius: 999px;
  background: linear-gradient(135deg, var(--amber), #ffc94a);
  box-shadow: 0 8px 24px rgba(249, 184, 24, 0.35);
  transition: transform 0.4s var(--ease);
}

.toggle[data-billing="yearly"] .toggle-thumb {
  transform: translateX(100%);
}

.toggle-option {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--muted);
}

.toggle[data-billing="monthly"] .toggle-option[data-value="monthly"],
.toggle[data-billing="yearly"] .toggle-option[data-value="yearly"] {
  color: #0c0f14;
}

.save-badge {
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  background: rgba(45, 212, 191, 0.14);
  border: 1px solid rgba(45, 212, 191, 0.35);
  color: var(--teal);
  font-size: 0.78rem;
  font-weight: 700;
  opacity: 0;
  transform: translateY(4px) scale(0.96);
  transition: 0.3s var(--ease);
}
.save-badge.is-visible {
  opacity: 1;
  transform: none;
}

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.1rem;
}

.card {
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  border-radius: 1.35rem;
  border: 1px solid var(--border);
  background: linear-gradient(165deg, rgba(255,255,255,0.07), rgba(255,255,255,0.02));
}

.card.is-featured {
  border-color: rgba(249, 184, 24, 0.45);
  box-shadow: 0 24px 60px rgba(249, 184, 24, 0.12);
}

.price {
  font-size: clamp(2.4rem, 4vw, 3.1rem);
  font-weight: 800;
  transition: transform 0.25s var(--ease), opacity 0.25s ease;
}
.price.is-updating {
  opacity: 0;
  transform: translateY(8px);
}

.billed {
  min-height: 1.25rem;
  color: var(--teal);
  font-weight: 600;
  opacity: 0;
}
.billed.is-visible { opacity: 1; }

.cta {
  margin-top: auto;
  display: inline-flex;
  justify-content: center;
  padding: 0.85rem 1rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  text-decoration: none;
  font-weight: 700;
  color: var(--text);
}
.cta.is-primary {
  color: #0c0f14;
  border: none;
  background: linear-gradient(135deg, var(--amber), #ffc94a);
}

@media (max-width: 900px) {
  .cards {
    grid-template-columns: 1fr;
    max-width: 420px;
    margin-inline: auto;
  }
  .card.is-featured { order: -1; }
}

JavaScript code

js
(function () {
  const toggle = document.getElementById("billingToggle");
  const saveBadge = document.getElementById("saveBadge");
  const labelMonthly = document.getElementById("labelMonthly");
  const labelYearly = document.getElementById("labelYearly");
  const cards = [...document.querySelectorAll(".card")];

  function animatePrice(el, nextValue) {
    el.classList.add("is-updating");
    window.setTimeout(() => {
      el.textContent = String(nextValue);
      el.classList.remove("is-updating");
    }, 160);
  }

  function setBilling(mode) {
    const yearly = mode === "yearly";
    toggle.dataset.billing = mode;
    toggle.setAttribute("aria-checked", yearly ? "true" : "false");
    saveBadge.classList.toggle("is-visible", yearly);
    labelMonthly.classList.toggle("is-active", !yearly);
    labelYearly.classList.toggle("is-active", yearly);

    cards.forEach((card) => {
      const monthly = Number(card.dataset.monthly);
      const yearlyPrice = Number(card.dataset.yearly);
      const priceEl = card.querySelector("[data-price]");
      const billedEl = card.querySelector("[data-billed]");
      const next = yearly ? yearlyPrice : monthly;
      animatePrice(priceEl, next);
      billedEl.classList.toggle("is-visible", yearly);
      if (yearly) {
        billedEl.textContent =
          "Billed $" + (yearlyPrice * 12).toLocaleString("en-US") + " / year";
      }
    });
  }

  toggle.addEventListener("click", () => {
    setBilling(toggle.dataset.billing === "monthly" ? "yearly" : "monthly");
  });

  setBilling("monthly");
})();

Full demo file

Want everything in one file (fonts, layout, cards, toggle, script)?

Open the complete animated pricing toggle demo

Right-click → Save As, or view source and copy.

Customize for your brand

  • Change --amber, --coral, and --teal in :root
  • Edit data-monthly / data-yearly on each card
  • Update the save badge text (Save 20% → your real discount)
  • Add or remove .features list items
  • Mark another plan with .is-featured + .plan-tag

Accessibility notes

  • Toggle uses role="switch" and aria-checked
  • Save badge uses aria-live="polite" so screen readers hear the discount
  • Focus ring appears on keyboard focus (:focus-visible)
  • Prices remain readable without the animation if JS is delayed

Common mistakes

  • Setting yearly data-yearly as the full annual total instead of the monthly equivalent
  • Forgetting position: relative on .toggle (thumb won’t slide correctly)
  • Nesting cards inside an overflow-hidden parent that clips hover lift
  • Hard-coding prices in HTML without updating data-* attributes the script reads

FAQs

Can I use this pricing toggle commercially?

Yes. Free for personal and commercial projects.

Does it work in WordPress / Elementor?

Yes. Paste the HTML into an HTML widget and enqueue (or inline) the CSS and JS.

How do I change the discount percent?

Update the badge text and your data-yearly values so the math matches the promise.

Will the animation hurt Core Web Vitals?

No meaningful impact. The script is tiny and only toggles classes plus text updates.

Next free packs

See more copy-paste UI in the Get Code library, including the sticky glass navbar with scroll progress. Need a custom pricing section for your brand? Book a consultation.

Frequently asked questions

04 on file

Yes. Free for personal and commercial projects.

Continue

Adjacent reads

Talal Mehmood portrait

Written by

Talal Mehmood

Founder & CEO

BSCS student from Pakistan. Freelancing since 2018 across web development, marketing, SEO, and finance. Founder of Let Start Design.

View author page
Signal // Leave a noteOpen

Join the conversation

Thoughts, pushback, or a win from applying this — we read every note.

Be constructive. Links welcome when relevant.

Thread

00 comments

  • No comments yet — be the first signal.
Free · 30 min

Ready when you are. Let’s talk.

No pitch deck — just a clear next step for your project.

Book Consultation