✦ A decade of Canvas craft, now driven by AI — describe it, watch it build live.Start building
Bootstrap 5Content

Bootstrap 5 Pricing Toggle

A Pricing Toggle is a UI control that switches a pricing section between two billing periods — typically monthly and annual — revealing different price figures without a page reload. It is composed from Bootstrap 5 primitives including btn-group or form-check for the switch, collapse or data-attribute toggling for content visibility, and card or list-group for the pricing tiers. Use it on any SaaS, subscription, or membership pricing page where showing both billing options side by side would add visual clutter.

Primary Class

btn-group / form-check form-switch

Common Use Cases

  • A SaaS product page where users switch between monthly and annual subscription prices to see the discounted yearly rate before choosing a plan.
  • A gym membership site that lets visitors toggle between pay-as-you-go monthly pricing and a discounted 12-month commitment to encourage annual sign-ups.
  • A cloud hosting provider displaying monthly on-demand pricing versus reserved-instance annual pricing for each server tier so engineers can compare total cost of ownership.
  • A project management tool onboarding flow where new users choose their billing cycle before entering payment details, with prices updating immediately on toggle.

Variants & Classes

VariantDescription
Button Group ToggleTwo pill-shaped buttons (Monthly / Annual) styled with btn-group. The active button uses btn-primary; the inactive uses btn-outline-secondary. JavaScript swaps a .d-none class on the relevant price elements.
Form Switch ToggleA single Bootstrap form-check form-switch flanked by 'Monthly' and 'Annual' labels. Checking the switch hides monthly prices and shows annual prices via JavaScript toggling .d-none. Accessible via role and aria-checked.
Toggle with Savings BadgeExtends the button group variant by appending a badge next to the 'Annual' label showing the percentage saving (e.g. 'Save 20%'). Uses badge bg-success rounded-pill positioned with d-inline-flex align-items-center gap-2.
Card Grid Pricing with ToggleThree Bootstrap cards in a row (col-md-4) each containing two price spans — one for monthly, one for annual — with .price-monthly and .price-annual classes. The toggle adds d-none to the inactive set across all cards simultaneously.

Code Example

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Pricing Toggle</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
  <style>
    .pricing-card { transition: transform .2s; }
    .pricing-card:hover { transform: translateY(-4px); }
    .price-display { font-size: 2.5rem; font-weight: 700; line-height: 1; }
    .price-period { font-size: .875rem; color: #6c757d; }
  </style>
</head>
<body class="bg-light py-5">
<div class="container">

  <!-- Toggle control -->
  <div class="d-flex justify-content-center align-items-center gap-3 mb-5">
    <span class="fw-semibold" id="label-monthly">Monthly</span>
    <div class="form-check form-switch mb-0">
      <input class="form-check-input" type="checkbox" role="switch"
             id="billingToggle" style="width:3rem;height:1.5rem;cursor:pointer;"
             aria-label="Toggle annual billing">
    </div>
    <span class="fw-semibold" id="label-annual">
      Annual
      <span class="badge bg-success rounded-pill ms-1" style="font-size:.7rem;">Save 20%</span>
    </span>
  </div>

  <!-- Pricing cards -->
  <div class="row g-4 justify-content-center">

    <!-- Starter -->
    <div class="col-12 col-md-4">
      <div class="card h-100 shadow-sm pricing-card">
        <div class="card-body p-4">
          <h5 class="card-title fw-bold">Starter</h5>
          <p class="text-muted small">For individuals and small projects.</p>
          <div class="my-3">
            <span class="price-display price-monthly">£9</span>
            <span class="price-display price-annual d-none">£7</span>
            <span class="price-period">&nbsp;/ mo</span>
          </div>
          <p class="text-muted x-small price-annual d-none">Billed as £84 / year</p>
          <ul class="list-unstyled mb-4">
            <li class="mb-1">&#10003; 3 projects</li>
            <li class="mb-1">&#10003; 5 GB storage</li>
            <li class="mb-1">&#10003; Email support</li>
          </ul>
          <a href="#" class="btn btn-outline-primary w-100">Get started</a>
        </div>
      </div>
    </div>

    <!-- Pro (highlighted) -->
    <div class="col-12 col-md-4">
      <div class="card h-100 shadow border-primary pricing-card">
        <div class="card-header bg-primary text-white text-center fw-semibold">Most Popular</div>
        <div class="card-body p-4">
          <h5 class="card-title fw-bold">Pro</h5>
          <p class="text-muted small">For growing teams and businesses.</p>
          <div class="my-3">
            <span class="price-display price-monthly">£29</span>
            <span class="price-display price-annual d-none">£23</span>
            <span class="price-period">&nbsp;/ mo</span>
          </div>
          <p class="text-muted small price-annual d-none">Billed as £276 / year</p>
          <ul class="list-unstyled mb-4">
            <li class="mb-1">&#10003; Unlimited projects</li>
            <li class="mb-1">&#10003; 50 GB storage</li>
            <li class="mb-1">&#10003; Priority support</li>
          </ul>
          <a href="#" class="btn btn-primary w-100">Get started</a>
        </div>
      </div>
    </div>

    <!-- Enterprise -->
    <div class="col-12 col-md-4">
      <div class="card h-100 shadow-sm pricing-card">
        <div class="card-body p-4">
          <h5 class="card-title fw-bold">Enterprise</h5>
          <p class="text-muted small">For large organisations with custom needs.</p>
          <div class="my-3">
            <span class="price-display price-monthly">£79</span>
            <span class="price-display price-annual d-none">£63</span>
            <span class="price-period">&nbsp;/ mo</span>
          </div>
          <p class="text-muted small price-annual d-none">Billed as £756 / year</p>
          <ul class="list-unstyled mb-4">
            <li class="mb-1">&#10003; Unlimited everything</li>
            <li class="mb-1">&#10003; 500 GB storage</li>
            <li class="mb-1">&#10003; Dedicated account manager</li>
          </ul>
          <a href="#" class="btn btn-outline-primary w-100">Contact sales</a>
        </div>
      </div>
    </div>

  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
  (function () {
    const toggle = document.getElementById('billingToggle');
    const monthly = document.querySelectorAll('.price-monthly');
    const annual = document.querySelectorAll('.price-annual');

    toggle.addEventListener('change', function () {
      const isAnnual = this.checked;
      monthly.forEach(el => el.classList.toggle('d-none', isAnnual));
      annual.forEach(el => el.classList.toggle('d-none', !isAnnual));
    });
  })();
</script>
</body>
</html>

Live Examples

Button Group Toggle

Two buttons in a btn-group act as the billing period selector. JavaScript swaps d-none on .price-monthly and .price-annual spans when a button is clicked.

Example 1

Compact Inline Switch with Labels

A minimal form-switch flanked by inline text labels. Suitable for embedding inside an existing pricing section without a card grid.

Example 2

Toggle with Animated Price Transition

Adds a brief CSS opacity fade when prices switch, giving the toggle a polished feel without any additional libraries.

Example 3

Canvas Framework Variants

The Canvas template extends Bootstrap 5 with 1,658+ component variants. Generate any of these using Canvas Builder:

  • Canvas Builder generates a three-column card grid pricing toggle from a prompt such as 'monthly annual pricing switcher with three tiers'
  • Ask Canvas Builder for a dark-mode pricing toggle and it outputs the same pattern with bg-dark card and text-light utilities applied throughout
  • Canvas Builder can scaffold a pricing toggle with a pre-selected annual tab and a 'Most Popular' badge on the middle card in one prompt
  • Prompt Canvas Builder with 'pricing table toggle no JavaScript' to receive a version that uses hidden radio inputs and CSS :checked selectors instead
  • Canvas Builder supports a compact single-product pricing toggle variant suitable for embedding inside a hero section rather than a full pricing page

Best Practices

Use data attributes to associate prices with their period

Rather than relying on DOM order, add data-billing="monthly" and data-billing="annual" to each price element. Your toggle script can then query document.querySelectorAll('[data-billing]') and filter by value, making the markup far easier to extend when you add a third tier or a quarterly option later.

Preserve layout height to avoid content shift

When monthly and annual price strings differ in character length (e.g. '£9' vs '£63'), hiding one and showing the other can cause the card to resize and shift surrounding content. Set a min-height on the price container or use CSS visibility: hidden instead of d-none for the inactive price so the space is always reserved.

Ensure keyboard and screen-reader accessibility

The form-switch input is natively focusable and announces its checked state to screen readers, but add an aria-label describing the action (e.g. 'Switch to annual billing') because the flanking text labels are not programmatically associated with the input. If you use a btn-group instead, apply aria-pressed='true'/'false' to each button and update it in your change handler.

Reflect the selected period in the CTA URL

Append a query parameter such as ?billing=annual to each plan's sign-up link when the annual toggle is active. This lets your back-end or landing page pre-select the correct billing option on the checkout page, reducing friction and abandonment.

FAQ

Does Bootstrap 5 have a built-in pricing toggle component?
No. Bootstrap 5 ships no official pricing toggle. This pattern is assembled from Bootstrap primitives: form-check form-switch or btn-group for the control, d-none for visibility toggling, and card or list-group for the price tiers. All the interactivity is handled by a small vanilla JavaScript snippet.
How do I make the prices update without writing JavaScript?
You can use the CSS :checked pseudo-class on a hidden radio input or checkbox. Wrap each billing section in a sibling element and use an adjacent sibling combinator in your stylesheet to show or hide content based on the input's checked state. This approach works without any JavaScript but requires all price elements to be siblings of the input in the DOM, which can complicate markup structure.
Can I animate the price change when the toggle is switched?
Yes. Instead of using d-none (which applies display:none and cannot be transitioned), keep both price elements in the DOM and toggle opacity and height via JavaScript or CSS custom properties. Set transition: opacity .25s ease on the price container and change opacity from 1 to 0 before swapping values, then back to 1. The third example on this page demonstrates this pattern in full.
How should I handle more than two billing periods, such as monthly, quarterly, and annual?
Replace the binary toggle with a three-button btn-group or a Bootstrap nav-tabs control. Assign each price element a data-billing attribute matching the period name. In your click handler, hide all price elements first with querySelectorAll('[data-billing]') then show only those matching the clicked period. This scales cleanly to any number of billing cycles without restructuring your card markup.