Bootstrap 5 pricing table from scratch, covering structure, visual hierarchy, accessibility, and the conversion patterns that genuinely move the needle.
Key Takeaways
- A three-column Bootstrap 5 pricing table built on the
col-lg-4grid gives you a proven, responsive starting point. - The “featured” plan must stand out visually using background, border, or elevation, not just a badge.
- Semantic HTML and ARIA labels keep your pricing table accessible to screen-reader users.
- Monthly/annual billing toggles can be built with Bootstrap 5’s native tab or button-group components, no extra libraries needed.
- CTA button colour, copy, and placement all have measurable effects on conversion; treat them as variables to test.
Why Most Pricing Tables Fail to Convert
Most pricing table failures trace back to three problems: too many tiers, visual parity between plans, and CTA buttons that look like footnotes. Research consistently shows three tiers outperform two or four. Two forces a binary choice; four creates decision paralysis. Visual parity is the subtler problem. When every column shares the same card style, weight, and colour, users scan left to right with no natural anchor. The result is longer time-to-decision and higher abandonment.
A pricing table HTML structure should guide the eye to the recommended plan first, then let the user compare up and down from there. Everything in the implementation below is built around that principle.

The Base HTML Structure
Start with Bootstrap 5’s grid: three equal columns on large screens, stacked on mobile. Each plan lives inside a .card, giving you consistent padding, border, and shadow utilities out of the box.
<section class="py-5 bg-light">
<div class="container">
<div class="row g-4 justify-content-center align-items-stretch">
<!-- Starter Plan -->
<div class="col-lg-4 col-md-6">
<div class="card h-100 border shadow-sm">
<div class="card-body d-flex flex-column p-4">
<h2 class="h5 fw-bold mb-1">Starter</h2>
<p class="text-muted small mb-3">For individuals and small projects</p>
<div class="mb-4">
<span class="display-5 fw-bold">$9</span>
<span class="text-muted">/ month</span>
</div>
<ul class="list-unstyled mb-4 flex-grow-1">
<li class="mb-2"><span class="text-success me-2">✓</span>5 projects</li>
<li class="mb-2"><span class="text-success me-2">✓</span>10 GB storage</li>
<li class="mb-2 text-muted"><span class="me-2">✗</span>Priority support</li>
<li class="mb-2 text-muted"><span class="me-2">✗</span>Custom domain</li>
</ul>
<a href="#" class="btn btn-outline-primary w-100 mt-auto">Get Started</a>
</div>
</div>
</div>
<!-- Pro Plan (Featured) -->
<div class="col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-lg text-white" style="background: var(--cnvs-themecolor);">
<div class="card-body d-flex flex-column p-4">
<span class="badge bg-white text-dark mb-3 align-self-start" style="color: var(--cnvs-themecolor) !important;">Most Popular</span>
<h2 class="h5 fw-bold mb-1">Pro</h2>
<p class="mb-3 opacity-75">For growing teams and businesses</p>
<div class="mb-4">
<span class="display-5 fw-bold">$29</span>
<span class="opacity-75">/ month</span>
</div>
<ul class="list-unstyled mb-4 flex-grow-1">
<li class="mb-2"><span class="me-2">✓</span>Unlimited projects</li>
<li class="mb-2"><span class="me-2">✓</span>100 GB storage</li>
<li class="mb-2"><span class="me-2">✓</span>Priority support</li>
<li class="mb-2 opacity-75"><span class="me-2">✗</span>Custom domain</li>
</ul>
<a href="#" class="btn btn-light w-100 mt-auto fw-semibold">Get Started</a>
</div>
</div>
</div>
<!-- Enterprise Plan -->
<div class="col-lg-4 col-md-6">
<div class="card h-100 border shadow-sm">
<div class="card-body d-flex flex-column p-4">
<h2 class="h5 fw-bold mb-1">Enterprise</h2>
<p class="text-muted small mb-3">For large-scale organisations</p>
<div class="mb-4">
<span class="display-5 fw-bold">$79</span>
<span class="text-muted">/ month</span>
</div>
<ul class="list-unstyled mb-4 flex-grow-1">
<li class="mb-2"><span class="text-success me-2">✓</span>Unlimited projects</li>
<li class="mb-2"><span class="text-success me-2">✓</span>1 TB storage</li>
<li class="mb-2"><span class="text-success me-2">✓</span>Priority support</li>
<li class="mb-2"><span class="text-success me-2">✓</span>Custom domain</li>
</ul>
<a href="#" class="btn btn-outline-primary w-100 mt-auto">Contact Sales</a>
</div>
</div>
</div>
</div>
</div>
</section>
The align-items-stretch on the row combined with h-100 on each card ensures all three columns reach the same height regardless of feature list length. This is a common visual bug when content varies between tiers. The d-flex flex-column plus mt-auto on the CTA button pins it consistently to each card’s bottom edge.
Adding a Monthly and Annual Billing Toggle
Annual plans improve cashflow and reduce churn, so a billing toggle earns its place on almost any pricing page. Bootstrap 5’s button group handles the UI without extra dependencies. Price swapping is handled with a short vanilla JavaScript function.
<div class="d-flex justify-content-center mb-5">
<div class="btn-group" role="group" aria-label="Billing period">
<input type="radio" class="btn-check" name="billing" id="monthly" autocomplete="off" checked>
<label class="btn btn-outline-primary" for="monthly">Monthly</label>
<input type="radio" class="btn-check" name="billing" id="annual" autocomplete="off">
<label class="btn btn-outline-primary" for="annual">
Annual <span class="badge bg-success ms-1">Save 20%</span>
</label>
</div>
</div>
<script>
const prices = {
monthly: ['$9', '$29', '$79'],
annual: ['$7', '$23', '$63']
};
document.querySelectorAll('input[name="billing"]').forEach(function(radio) {
radio.addEventListener('change', function() {
const period = this.id; // 'monthly' or 'annual'
document.querySelectorAll('.price-amount').forEach(function(el, i) {
el.textContent = prices[period][i];
});
});
});
</script>
Add the class price-amount to each <span> wrapping the dollar figure in the card body. The script iterates over them in DOM order, so keep your cards in the correct left-to-right sequence.

Visual Hierarchy and the Featured Plan
The featured card above uses --cnvs-themecolor, the CSS custom property exposed by the Canvas HTML Template. Changing that one variable in your root stylesheet updates the highlight colour across the entire pricing section, which is useful when building for clients or white-label projects.
If you are not using Canvas, replace it with a fixed hex value or your own CSS variable. The principle is that the featured card must differ in at least two visual dimensions. Background colour is the primary signal, but pairing it with a heavier shadow (shadow-lg versus shadow-sm) reinforces the hierarchy for users with colour vision deficiencies.
A “Most Popular” badge alone is a weak signal. Full background contrast is the reliable approach. For more on building consistent visual sections, the post on 9 Bootstrap 5 card layouts for portfolios and blogs covers card variation patterns you can adapt here.
Accessibility Requirements for Pricing Tables
Pricing tables have specific accessibility considerations that standard Bootstrap card documentation does not cover. The main issues are heading hierarchy, button label uniqueness, and toggle state.
- Heading hierarchy: Use
<h2>for plan names only if your page heading is<h1>. Do not skip levels. If the pricing section sits below an<h2>section heading, use<h3>for plan names. - Unique CTA labels: Three buttons all labelled “Get Started” are ambiguous for screen-reader users navigating by button. Add a visually hidden
<span>:<span class="visually-hidden">for the Pro plan</span>. - Toggle ARIA state: The
btn-checkradio inputs handlearia-checkednatively, but confirm this in your browser’s accessibility tree before shipping. - Feature lists: Unavailable features marked with a cross should include a
<span class="visually-hidden">Not included:</span>prefix so the list reads coherently out loud.
CTA Copy, Colour, and Placement
The button is where conversion actually happens, and it deserves as much attention as the pricing numbers themselves. Several patterns have consistent support across published A/B test data.
- Action-oriented copy: “Get Started” outperforms “Subscribe” because it implies lower commitment. “Start Free Trial” outperforms both when a trial exists. Match the copy to the actual next step.
- Button colour contrast: On the featured dark-background card, use a light button. On neutral cards, use a filled or outlined brand-colour button. Never use grey buttons for primary CTAs.
- Bottom-pinned placement: The
mt-autotechnique keeps all CTAs at the same vertical position. That alignment is a subtle trust signal: it shows the layout was intentional rather than assembled hastily. - Secondary CTA for Enterprise: “Contact Sales” is the right copy for a high-touch tier. It sets correct expectations and reduces bounce from users who see a price they cannot self-serve.
If your pricing page includes social proof such as testimonials or logos, the post on how to create a Bootstrap 5 testimonial slider covers a component that pairs well directly below the pricing table.
What to Avoid in Production Pricing Tables
A few patterns look reasonable in design tools but cause real problems once deployed.
- Inline styles for brand colours: The code above uses
style="background: var(--cnvs-themecolor);"for illustration. In a real project, move this to a utility class or component stylesheet so it is overridable and themeable without editing markup. - Feature comparison tables inside cards: Embedding a full feature matrix inside collapsed cards on mobile is a known UX trap. If you have more than eight feature rows to compare, put a separate comparison table below the cards rather than cramming it into them.
- JavaScript-only price formatting: The toggle script above assumes DOM order matches your price array. If you later reorder columns or conditionally hide a tier, the mapping breaks silently. Use
data-price-monthlyanddata-price-annualattributes on each price element and read them by attribute rather than by index. - Missing currency localisation: Hard-coded dollar signs fail international visitors. If you serve multiple regions, store price values in a data attribute and handle currency symbols via a localisation lookup (even a simple object literal) rather than embedding them in the text node.
Frequently Asked Questions
Use col-lg-4 col-md-6 on each column so the layout switches from three columns on large screens to two on medium screens and a single stack on small screens. Add g-4 to the row for consistent gutters at every breakpoint. Avoid fixed pixel widths on cards.
Apply a full background colour change using your brand colour, paired with a heavier shadow class such as shadow-lg. Do not rely on a badge alone. Two distinct visual signals (background and elevation) ensure the recommendation is clear for users with colour vision deficiencies as well.
Yes. Bootstrap 5 dropped the jQuery dependency entirely. The button-group radio pattern shown in this article uses Bootstrap 5’s btn-check component and a short vanilla JavaScript event listener. No additional libraries are needed.
Bootstrap 5 exposes component-level CSS variables on each component class. You can also define your own variables at :root level and reference them in inline styles or utility classes. In Canvas, --cnvs-themecolor is set globally and can be overridden per-section by redefining it on a wrapper element, which is useful for theming individual pricing blocks.
Three tiers (Starter, Pro, Enterprise) is the most commonly effective pattern. Two tiers forces a binary decision that can feel under-specified. Four or more tiers introduce comparison fatigue. If your product genuinely requires more granularity, use three visible tiers and link to a full comparison page rather than extending the card row.
Looking for a production-ready Bootstrap 5 HTML template? Browse Canvas Template demos and find the perfect starting point for your next project.
If you’re building with the Canvas HTML Template and want to ship production-ready Bootstrap 5 layouts faster, try Canvas Builder free — the visual builder that exports clean Canvas-ready markup in minutes.
Skip the setup and build it free
Spin up a complete Bootstrap 5 site, blog included, with Canvas Builder. No coding, no cost.
Canvas Team
Tutorials and tips for building beautiful Bootstrap 5 websites with the Canvas HTML Template and Canvas Builder.
More from the Canvas Blog