Get a free, responsive pricing table template. Just copy and paste the HTML, Tailwind CSS, and JavaScript to add a modern, interactive price page to your site.
Table of Contents
A pricing table is one of the most important parts of any business or SaaS website. It helps users easily compare plans and select the one that fits their needs.
In this post, we’ll create a responsive and interactive pricing table using:
- HTML for the structure
- Tailwind CSS for quick styling
- Custom CSS for unique animations and effects
- JavaScript for interactive elements like toggles and dynamic pricing updates
There are no extra libraries required except Tailwind CSS, which we’ll include using a simple CDN link:
<script src="https://cdn.tailwindcss.com"></script>Source Code
Step 1 (HTML Code):
This is the main structure of our pricing page. It includes the layout, cards, toggles, and price display area. All the necessary scripts (Tailwind, Google Fonts) are linked in the <head>.
Step 2 (CSS Code):
This CSS works with Tailwind to create the custom styles for the toggle, cards, checkboxes, and slider.
Paste this code into a new file named styles.css.
Then, make sure to link it in the <head> of your HTML file.:
body {
font-family: 'Inter', sans-serif;
background-color: #f9fafb; /* gray-50 */
color: #111827; /* gray-900 */
}
/* --- Custom Toggle Switch Styling --- */
.toggle-knob {
transition: transform 0.3s ease-in-out;
}
input:checked + .toggle-slider .toggle-knob {
transform: translateX(24px); /* 6 * 4px = 24px */
}
input:checked + .toggle-slider {
background-color: #4f46e5; /* indigo-600 */
}
/* --- Plan Selector Card (Left Side) --- */
.plan-selector-card {
background-color: #ffffff;
border: 2px solid #e5e7eb; /* gray-200 */
border-radius: 1rem; /* rounded-2xl */
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
transform: scale(1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05),
0 2px 4px -1px rgba(0, 0, 0, 0.03); /* Added subtle shadow */
}
.plan-selector-card:hover {
border-color: #a5b4fc; /* indigo-300 */
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05);
}
.plan-selector-card.active {
border-color: #4f46e5; /* indigo-600 */
box-shadow: 0 20px 25px -5px rgba(79, 70, 229, 0.1),
0 10px 10px -5px rgba(79, 70, 229, 0.04);
transform: scale(1.03);
background-color: #f0f5ff; /* A very light indigo */
}
/* --- Custom Add-On Checkbox Styling --- */
.add-on-label {
display: flex;
align-items: center;
padding: 1rem;
background-color: #f9fafb; /* gray-50 */
border-radius: 0.75rem; /* rounded-lg */
border: 2px solid #f3f4f6; /* gray-100 */
cursor: pointer;
transition: all 0.2s ease;
}
.add-on-label:hover {
border-color: #d1d5db; /* gray-300 */
}
.add-on-checkbox {
-webkit-appearance: none;
appearance: none;
height: 1.5rem; /* h-6 */
width: 1.5rem; /* w-6 */
flex-shrink: 0;
background-color: #ffffff;
border: 2px solid #d1d5db; /* gray-300 */
border-radius: 0.375rem; /* rounded-md */
margin-right: 0.75rem; /* mr-3 */
display: grid;
place-content: center;
transition: all 0.2s ease;
}
.add-on-checkbox:checked {
background-color: #4f46e5; /* indigo-600 */
border-color: #4f46e5;
}
.add-on-checkbox::before {
content: '';
width: 0.8rem;
height: 0.8rem;
transform: scale(0);
transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em #ffffff;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
.add-on-checkbox:checked::before {
transform: scale(1);
}
.add-on-label.peer-checked {
border-color: #4f46e5;
background-color: #f0f5ff;
}
/* --- Dynamic Feature List (Right Side) --- */
.feature-item {
display: flex;
align-items: center;
padding: 0.75rem 0; /* py-3 */
border-bottom: 1px solid #f3f4f6; /* gray-100 */
transition: all 0.3s ease-in-out;
opacity: 1;
}
.feature-item.disabled {
opacity: 0.6;
}
.feature-item .feature-name {
transition: all 0.3s ease;
}
.feature-item.disabled .feature-name {
text-decoration: line-through;
color: #6b7280; /* gray-500 */
}
.icon-wrapper {
width: 1.25rem; /* w-5 */
height: 1.25rem; /* h-5 */
margin-right: 0.75rem; /* mr-3 */
flex-shrink: 0;
position: relative;
}
.icon-check,
.icon-cross {
position: absolute;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.icon-check {
color: #4f46e5; /* indigo-600 */
opacity: 1;
transform: scale(1);
}
.icon-cross {
color: #9ca3af; /* gray-400 */
opacity: 0;
transform: scale(0.5);
}
.feature-item.disabled .icon-check {
opacity: 0;
transform: scale(0.5);
}
.feature-item.disabled .icon-cross {
opacity: 1;
transform: scale(1);
}
/* --- Animated Price Counter --- */
#plan-price-value {
transition: all 0.3s ease;
}
/* --- Custom Range Slider --- */
.price-slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 8px;
background: #e5e7eb; /* gray-200 */
border-radius: 9999px;
outline: none;
transition: opacity 0.2s;
}
/* Thumb (Chrome, Safari, etc.) */
.price-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
background: #4f46e5; /* indigo-600 */
border-radius: 50%;
cursor: pointer;
border: 4px solid #ffffff;
box-shadow: 0 4px 10px rgba(79, 70, 229, 0.2);
transition: all 0.2s ease;
}
.price-slider:hover::-webkit-slider-thumb {
transform: scale(1.1);
}
/* Thumb (Firefox) */
.price-slider::-moz-range-thumb {
width: 24px;
height: 24px;
background: #4f46e5;
border-radius: 50%;
cursor: pointer;
border: 4px solid #ffffff;
box-shadow: 0 4px 10px rgba(79, 70, 229, 0.2);
transition: all 0.2s ease;
}
.price-slider:hover::-moz-range-thumb {
transform: scale(1.1);
}
Step 3 (JavaScript Code):
<p>This is the "brains" of the operation, rewritten to use per-user pricing and to default to the "Pro" plan. Place this in the <code><script></code> tag at the bottom of your <code><body></code>.</p>document.addEventListener('DOMContentLoaded', () => {
// --- DATA ---
const plansData = {
starter: {
name: 'Starter',
description: 'Ideal for individuals getting started and hobby projects.',
monthlyPricePerUser: 29,
annualPricePerUser: 23,
minUsers: 1,
maxUsers: 10,
defaultUsers: 5,
ctaText: 'Get Started with Starter',
},
pro: {
name: 'Pro',
description:
'For growing teams and professionals who need advanced tools.',
monthlyPricePerUser: 79,
annualPricePerUser: 63,
minUsers: 5,
maxUsers: 50,
defaultUsers: 10,
ctaText: 'Choose Pro',
},
enterprise: {
name: 'Enterprise',
description:
'Tailored solutions for large organizations and complex needs.',
monthlyPrice: 'Custom',
annualPrice: 'Custom',
minUsers: 50,
maxUsers: 500,
defaultUsers: 50,
ctaText: 'Contact Sales',
},
};
const addOnsData = {
ai: {
monthlyPrice: 10,
annualPrice: 8,
},
support: {
monthlyPrice: 15,
annualPrice: 12,
},
};
const allFeatures = [
{ id: 'projects', name: 'Up to 10 Projects', plans: ['starter'] },
{
id: 'projects-pro',
name: 'Unlimited Projects',
plans: ['pro', 'enterprise'],
},
{ id: 'team-members', name: '5 Team Members', plans: ['starter'] },
{
id: 'team-members-pro',
name: 'Unlimited Team Members',
plans: ['pro', 'enterprise'],
},
{ id: 'storage', name: '50GB Storage', plans: ['starter', 'pro'] },
{ id: 'storage-ent', name: 'Unlimited Storage', plans: ['enterprise'] },
{
id: 'analytics',
name: 'Basic Analytics',
plans: ['starter', 'pro', 'enterprise'],
},
{ id: 'integrations', name: 'Basic Integrations', plans: ['starter'] },
{
id: 'integrations-pro',
name: 'Advanced Integrations',
plans: ['pro', 'enterprise'],
},
{ id: 'ai-tools', name: 'Advanced AI Tools', plans: ['pro', 'enterprise'] },
{ id: 'api-access', name: 'API Access', plans: ['pro', 'enterprise'] },
{
id: 'priority-support',
name: 'Priority Chat Support',
plans: ['pro', 'enterprise'],
},
{ id: 'sso', name: 'Advanced SSO & Security', plans: ['enterprise'] },
{ id: 'sla', name: 'Uptime SLA', plans: ['enterprise'] },
{
id: 'dedicated-support',
name: '24/7/356 Dedicated Support',
plans: ['enterprise'],
},
];
// --- DOM ELEMENTS ---
const toggle = document.getElementById('billing-toggle');
const planSelectors = document.querySelectorAll('.plan-selector-card');
const selectorPriceSpans = document.querySelectorAll(
'.plan-selector-card span[data-monthly-price]'
);
const planNameEl = document.getElementById('plan-name');
const planDescriptionEl = document.getElementById('plan-description');
const planPriceValueEl = document.getElementById('plan-price-value');
const planPricePeriodEl = document.getElementById('plan-price-period');
const ctaButton = document.getElementById('cta-button');
const featureListEl = document.getElementById('feature-list');
const sliderContainer = document.getElementById('slider-container');
const teamSlider = document.getElementById('team-slider');
const sliderValueDisplay = document.getElementById('slider-value-display');
const addOnsContainer = document.getElementById('add-ons-container');
const addOnCheckboxes = document.querySelectorAll('.add-on-checkbox');
const addOnPriceSpans = document.querySelectorAll('.add-on-price');
let currentPlanId = 'starter';
let isAnnual = false;
let priceAnimationInterval = null;
let activeAddOns = new Set();
// --- FUNCTIONS ---
// 1. Initialize: Render all possible features into the list
function initializeFeatureList() {
allFeatures.forEach((feature) => {
const li = document.createElement('li');
li.className = 'feature-item';
li.dataset.featureId = feature.id;
li.innerHTML = `
<div class="icon-wrapper">
<svg class="icon-check w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
<svg class="icon-cross w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
</div>
<span class="feature-name text-gray-700">${feature.name}</span>
`;
featureListEl.appendChild(li);
});
}
// 2. Animate Price Counter
function animatePrice(newPrice) {
if (typeof newPrice !== 'number') {
if (priceAnimationInterval) clearInterval(priceAnimationInterval);
planPriceValueEl.innerText = newPrice;
planPriceValueEl.dataset.currentPrice = 'NaN'; // Mark as non-numeric
return;
}
let currentPrice = parseInt(planPriceValueEl.dataset.currentPrice);
if (isNaN(currentPrice)) {
currentPrice = 0;
}
clearInterval(priceAnimationInterval); // Clear any existing animation
const step =
newPrice > currentPrice
? Math.ceil((newPrice - currentPrice) / 10)
: Math.floor((newPrice - currentPrice) / 10);
if (step === 0) {
// Already at target
planPriceValueEl.dataset.currentPrice = newPrice;
planPriceValueEl.innerText = newPrice; // Set text if no animation
return;
}
let animatedPrice = currentPrice; // Use a variable for animation state
priceAnimationInterval = setInterval(() => {
if (animatedPrice !== newPrice) {
animatedPrice += step;
// Overshoot/undershoot check
if (
(step > 0 && animatedPrice >= newPrice) ||
(step < 0 && animatedPrice <= newPrice)
) {
animatedPrice = newPrice;
clearInterval(priceAnimationInterval);
}
planPriceValueEl.innerText = animatedPrice; // Update text with animated value
} else {
clearInterval(priceAnimationInterval);
}
}, 20); // Animation speed
planPriceValueEl.dataset.currentPrice = newPrice; // Set the new *target* price
}
// 3. Update all UI components based on selected plan
function updatePlanDetails(planId) {
currentPlanId = planId;
const planData = plansData[planId];
isAnnual = toggle.checked;
// Reset add-ons when changing plans
activeAddOns.clear();
addOnCheckboxes.forEach((cb) => (cb.checked = false));
// Update text details
planNameEl.innerText = planData.name;
planDescriptionEl.innerText = planData.description;
ctaButton.innerText = planData.ctaText;
ctaButton.href = `#${planId}`;
// Show/Hide Add-ons & Slider
if (planId === 'enterprise') {
addOnsContainer.style.display = 'none';
sliderContainer.style.display = 'none';
} else {
addOnsContainer.style.display = 'block';
sliderContainer.style.display = 'block';
// Update slider properties
teamSlider.min = planData.minUsers;
teamSlider.max = planData.maxUsers;
teamSlider.value = planData.defaultUsers;
updateSliderDisplay();
}
// Update price (will be called by updateTotalPrice)
updateTotalPrice();
// Update feature list
allFeatures.forEach((feature) => {
const featureEl = document.querySelector(
`.feature-item[data-feature-id="${feature.id}"]`
);
if (feature.plans.includes(planId)) {
featureEl.classList.remove('disabled');
} else {
featureEl.classList.add('disabled');
}
});
// Update selector card active state
planSelectors.forEach((card) => {
if (card.dataset.planId === planId) {
card.classList.add('active');
} else {
card.classList.remove('active');
}
});
}
// 4. Update prices on the selector cards (left side)
function updateSelectorCardPrices() {
isAnnual = toggle.checked;
selectorPriceSpans.forEach((span) => {
const price = isAnnual
? span.dataset.annualPrice
: span.dataset.monthlyPrice;
span.innerText = `$${price}`;
});
}
// 5. Update Add-on price text
function updateAddOnPrices() {
isAnnual = toggle.checked;
addOnPriceSpans.forEach((span) => {
const monthly = span.dataset.monthlyPrice;
const annual = span.dataset.annualPrice;
span.innerText = isAnnual ? `$${annual}` : `$${monthly}`;
});
}
// 6. Calculate Total Price
function updateTotalPrice() {
const planData = plansData[currentPlanId];
isAnnual = toggle.checked;
// Handle Enterprise "Custom" price
if (currentPlanId === 'enterprise') {
animatePrice('Custom');
planPricePeriodEl.innerText = 'Custom Pricing';
return;
}
const selectedUsers = parseInt(teamSlider.value);
const perUserPrice = isAnnual
? planData.annualPricePerUser
: planData.monthlyPricePerUser;
const basePlanPrice = selectedUsers * perUserPrice;
let totalAddOnPrice = 0;
activeAddOns.forEach((addOnId) => {
const addOnData = addOnsData[addOnId];
// Making add-ons a flat monthly fee, not per-user
totalAddOnPrice += isAnnual
? addOnData.annualPrice
: addOnData.monthlyPrice;
});
const totalPrice = basePlanPrice + totalAddOnPrice;
animatePrice(totalPrice);
planPricePeriodEl.innerText = isAnnual ? 'per year' : 'per month';
}
// 7. Update Slider Value Display
function updateSliderDisplay() {
const users = teamSlider.value;
sliderValueDisplay.innerText = `${users} Member${users > 1 ? 's' : ''}`;
}
// --- EVENT LISTENERS ---
// Billing toggle
toggle.addEventListener('change', () => {
isAnnual = toggle.checked;
updateSelectorCardPrices();
updateAddOnPrices();
updateTotalPrice(); // Re-calculate total price
});
// Team slider
teamSlider.addEventListener('input', () => {
updateSliderDisplay();
updateTotalPrice();
});
// Plan selector cards
planSelectors.forEach((card) => {
card.addEventListener('click', () => {
const planId = card.dataset.planId;
updatePlanDetails(planId);
});
});
// Add-on checkboxes
addOnCheckboxes.forEach((checkbox) => {
checkbox.addEventListener('change', () => {
const addOnId = checkbox.dataset.addOnId;
if (checkbox.checked) {
activeAddOns.add(addOnId);
} else {
activeAddOns.delete(addOnId);
}
updateTotalPrice(); // Recalculate price on add-on change
});
});
// --- INITIALIZATION ---
initializeFeatureList();
updateSelectorCardPrices();
updateAddOnPrices();
// Set "Pro" as the default active plan on load
const defaultPlanId = 'pro';
currentPlanId = defaultPlanId;
document
.querySelector(`.plan-selector-card[data-plan-id="${defaultPlanId}"]`)
.classList.add('active');
updatePlanDetails(defaultPlanId);
// Set initial price without animation
const initialPlan = plansData[defaultPlanId];
const initialUsers = initialPlan.defaultUsers;
const initialPrice = initialPlan.monthlyPricePerUser * initialUsers;
planPriceValueEl.innerText = initialPrice;
planPriceValueEl.dataset.currentPrice = initialPrice;
teamSlider.value = initialUsers;
updateSliderDisplay();
});Final Output:
Conclusion:
And that's it! You now have a fully functional, responsive, and interactive pricing table that uses a per-user pricing model. Users can see exactly what they're getting and how much it will cost, all updated in real-time.
From here, you can easily customize this code. Try changing the plan data in the plansData JavaScript object to match your own products, or adjust the Tailwind CSS utility classes in the HTML to change the colors and spacing to match your brand.
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊


