Learn how to create a responsive pricing table with feature comparison using HTML, CSS, and JavaScript. Step-by-step guide for beginners.
Table of Contents
A pricing table with feature comparison is a key part of any SaaS, product, or eCommerce website. It helps users quickly compare plans and decide which one suits them best. Unlike basic tables, an advanced pricing table includes interactive elements like monthly/yearly toggle buttons, highlighted popular plans, and icons for feature status.
In this guide, we will create a modern, responsive, and advanced pricing table using HTML, CSS, and JavaScript with hover effects, animations, and a toggle feature for pricing.
Prerequisites:
Before you begin, ensure you have:
- Basic understanding of HTML, CSS, and JavaScript.
- A code editor (e.g., VS Code).
- Knowledge of Flexbox and CSS transitions.
- A browser for testing.
Source Code
Step 1 (HTML Code):
We will start by creating a simple HTML structure with 5 pricing plans and their features.
Step 2 (CSS Code):
Now, let’s style the table to make it look modern and responsive.
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&display=swap');
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: #e5e7eb;
}
html,
:host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
font-family: Inter, sans-serif;
font-feature-settings: normal;
font-variation-settings: normal;
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
line-height: inherit;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
button {
font-family: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
font-size: 100%;
font-weight: inherit;
line-height: inherit;
letter-spacing: inherit;
color: inherit;
margin: 0;
padding: 0;
text-transform: none;
}
button,
[role="button"] {
cursor: pointer;
}
.absolute{
position: absolute;
}
.relative{
position: relative;
}
.-top-3{
top: -0.85rem;
}
.left-1\/2{
left: 22%;
}
.z-10{
z-index: 10;
}
.mx-auto{
margin-left: auto;
margin-right: auto;
}
.-ml-16{
margin-left: -4rem;
}
.-mt-8{
margin-top: -2rem;
}
.mb-12{
margin-bottom: 3rem;
}
.mb-16{
margin-bottom: 4rem;
}
.mb-2{
margin-bottom: 0.5rem;
}
.mb-3{
margin-bottom: 0.75rem;
}
.mb-4{
margin-bottom: 1rem;
}
.mb-6{
margin-bottom: 1.5rem;
}
.mb-8{
margin-bottom: 2rem;
}
.ml-1{
margin-left: 0.25rem;
}
.ml-2{
margin-left: 0.5rem;
}
.mr-2{
margin-right: 0.5rem;
}
.mr-3{
margin-right: 0.75rem;
}
.mt-2{
margin-top: 0.5rem;
}
.mt-4{
margin-top: 1rem;
}
.mt-8{
margin-top: 2rem;
}
.block{
display: block;
}
.flex{
display: flex;
}
.inline-flex{
display: inline-flex;
}
.table{
display: table;
}
.grid{
display: grid;
}
.hidden{
display: none;
}
.h-16{
height: 4rem;
}
.h-4{
height: 1rem;
}
.h-5{
height: 1.25rem;
}
.h-8{
height: 2rem;
}
.w-4{
width: 1rem;
}
.w-5{
width: 1.25rem;
}
.w-8{
width: 2rem;
}
.w-full{
width: 100%;
}
.max-w-3xl{
max-width: 48rem;
}
.max-w-7xl{
max-width: 80rem;
}
.scale-105{
--tw-scale-x: 1.05;
--tw-scale-y: 1.05;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.transform{
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.cursor-help{
cursor: help;
}
.grid-cols-1{
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.items-center{
align-items: center;
}
.justify-center{
justify-content: center;
}
.justify-between{
justify-content: space-between;
}
.gap-6{
gap: 1.5rem;
}
.gap-8{
gap: 2rem;
}
.space-y-2 > :not([hidden]) ~ :not([hidden]){
--tw-space-y-reverse: 0;
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
}
.space-y-4 > :not([hidden]) ~ :not([hidden]){
--tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
.overflow-x-auto{
overflow-x: auto;
}
.rounded{
border-radius: 0.25rem;
}
.rounded-full{
border-radius: 9999px;
}
.rounded-lg{
border-radius: 0.5rem;
}
.rounded-md{
border-radius: 0.375rem;
}
.rounded-xl{
border-radius: 0.75rem;
}
.border{
border-width: 1px;
}
.border-2{
border-width: 2px;
}
.border-b{
border-bottom-width: 1px;
}
.border-l-2{
border-left-width: 2px;
}
.border-t{
border-top-width: 1px;
}
.border-accent-600{
--tw-border-opacity: 1;
border-color: rgb(5 150 105 / var(--tw-border-opacity, 1));
}
.border-secondary-100{
--tw-border-opacity: 1;
border-color: rgb(241 245 249 / var(--tw-border-opacity, 1));
}
.border-secondary-200{
--tw-border-opacity: 1;
border-color: rgb(226 232 240 / var(--tw-border-opacity, 1));
}
.border-secondary-600{
--tw-border-opacity: 1;
border-color: rgb(71 85 105 / var(--tw-border-opacity, 1));
}
.bg-accent-100{
--tw-bg-opacity: 1;
background-color: rgb(209 250 229 / var(--tw-bg-opacity, 1));
}
.bg-accent-600{
--tw-bg-opacity: 1;
background-color: rgb(5 150 105 / var(--tw-bg-opacity, 1));
}
.bg-background{
--tw-bg-opacity: 1;
background-color: rgb(250 251 252 / var(--tw-bg-opacity, 1));
}
.bg-primary-600{
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
}
.bg-secondary-100{
--tw-bg-opacity: 1;
background-color: rgb(233 233 233 / var(--tw-bg-opacity, 1));
}
.bg-secondary-600{
--tw-bg-opacity: 1;
background-color: rgb(71 85 105 / var(--tw-bg-opacity, 1));
}
.bg-surface{
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
}
.bg-text-primary{
--tw-bg-opacity: 1;
background-color: rgb(30 41 59 / var(--tw-bg-opacity, 1));
}
.bg-white{
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
}
.p-1{
padding: 0.25rem;
}
.p-6{
padding: 1.5rem;
}
.p-8{
padding: 2rem;
}
.px-2{
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.px-4{
padding-left: 1rem;
padding-right: 1rem;
}
.px-6{
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.py-1{
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.py-12{
padding-top: 3rem;
padding-bottom: 3rem;
}
.py-2{
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.py-3{
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.py-4{
padding-top: 1rem;
padding-bottom: 1rem;
}
.pb-6{
padding-bottom: 1.5rem;
}
.pl-4{
padding-left: 1rem;
}
.pt-8{
padding-top: 2rem;
}
.text-left{
text-align: left;
}
.text-center{
text-align: center;
}
.font-inter{
font-family: Inter, sans-serif;
}
.text-2xl{
font-size: 1.5rem;
line-height: 2rem;
}
.text-3xl{
font-size: 1.875rem;
line-height: 2.25rem;
}
.text-4xl{
font-size: 2.25rem;
line-height: 2.5rem;
}
.text-lg{
font-size: 1.125rem;
line-height: 1.75rem;
}
.text-sm{
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-xl{
font-size: 1.25rem;
line-height: 1.75rem;
}
.text-xs{
font-size: 0.75rem;
line-height: 1rem;
}
.font-bold{
font-weight: 700;
}
.font-medium{
font-weight: 500;
}
.font-semibold{
font-weight: 600;
}
.text-accent-600{
--tw-text-opacity: 1;
color: rgb(5 150 105 / var(--tw-text-opacity, 1));
}
.text-accent-700{
--tw-text-opacity: 1;
color: rgb(4 120 87 / var(--tw-text-opacity, 1));
}
.text-primary-500{
--tw-text-opacity: 1;
color: rgb(59 130 246 / var(--tw-text-opacity, 1));
}
.text-primary-600{
--tw-text-opacity: 1;
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
}
.text-secondary-300{
--tw-text-opacity: 1;
color: rgb(203 213 225 / var(--tw-text-opacity, 1));
}
.text-secondary-400{
--tw-text-opacity: 1;
color: rgb(148 163 184 / var(--tw-text-opacity, 1));
}
.text-text-primary{
--tw-text-opacity: 1;
color: rgb(30 41 59 / var(--tw-text-opacity, 1));
}
.text-text-secondary{
--tw-text-opacity: 1;
color: rgb(100 116 139 / var(--tw-text-opacity, 1));
}
.text-white{
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}
.shadow-elevated{
--tw-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--tw-shadow-colored: 0 4px 6px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-sm{
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.transition-all{
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.transition-transform{
transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.duration-200{
transition-duration: 200ms;
}
/* Reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Custom utility classes */
.transition-micro {
transition: all 150ms ease-out;
}
.transition-layout {
transition: all 300ms ease-in-out;
}
.shadow-elevated {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.hover\:bg-accent-700:hover{
--tw-bg-opacity: 1;
background-color: rgb(4 120 87 / var(--tw-bg-opacity, 1));
}
.hover\:bg-primary-700:hover{
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
}
.hover\:bg-secondary-200:hover{
--tw-bg-opacity: 1;
background-color: rgb(226 232 240 / var(--tw-bg-opacity, 1));
}
.hover\:text-primary-600:hover{
--tw-text-opacity: 1;
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
}
.hover\:text-white:hover{
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}
@media (min-width: 640px){
.sm\:px-6{
padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
@media (min-width: 768px){
.md\:flex{
display: flex;
}
.md\:grid-cols-2{
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.md\:grid-cols-4{
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.md\:text-5xl{
font-size: 3rem;
line-height: 1;
}
}
@media (min-width: 1024px){
.lg\:grid-cols-5{
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.lg\:gap-4{
gap: 1rem;
}
.lg\:px-8{
padding-left: 2rem;
padding-right: 2rem;
}
} Step 3 (JavaScript Code):
If you want to add a toggle feature (e.g., switching between monthly and yearly prices), use JavaScript:
1. Pricing Toggle Functionality
const pricingToggles = document.querySelectorAll('.pricing-toggle');
const monthlyPrices = document.querySelectorAll('.monthly-price');
const quarterlyPrices = document.querySelectorAll('.quarterly-price');
const annualPrices = document.querySelectorAll('.annual-price');
- pricingToggles: Selects all elements with the class .pricing-toggle. These are toggle buttons (e.g., Monthly / Quarterly / Annual).
- monthlyPrices, quarterlyPrices, annualPrices: Selects all elements where the price values will be displayed for each billing period.
const prices = {
monthly: { monthly: 49, quarterly: 124, annual: 441 },
quarterly: { monthly: 42, quarterly: 124, annual: 441 },
annual: { monthly: 37, quarterly: 111, annual: 441 },
};
- prices: An object storing price values for each billing period.
For example, when period = "monthly", the monthly price is $49, quarterly price is $124, and annual price is $441.
Adding Event Listeners to Each Toggle
pricingToggles.forEach((toggle) => {
toggle.addEventListener('click', () => {
// Remove active class from all toggles
pricingToggles.forEach((t) => {
t.classList.remove('active', 'bg-white', 'text-text-primary', 'shadow-sm');
t.classList.add('text-text-secondary');
});
// Add active class to clicked toggle
toggle.classList.add('active', 'bg-white', 'text-text-primary', 'shadow-sm');
toggle.classList.remove('text-text-secondary');
const period = toggle.dataset.period;
// Update prices based on selected period
monthlyPrices.forEach((price) => {
price.textContent = `$${prices[period].monthly}`;
});
quarterlyPrices.forEach((price) => {
price.textContent = `$${prices[period].quarterly}`;
});
annualPrices.forEach((price) => {
price.textContent = `$${prices[period].annual}`;
});
});
});
- pricingToggles.forEach: Loops through each toggle button.
- When clicked:
- Reset all buttons by removing the "active" and styling classes.
- Highlight the clicked toggle by adding active styles (bg-white, text-text-primary, etc.).
- const period = toggle.dataset.period: Reads the value of data-period attribute (e.g., "monthly" or "annual").
- Updates all .monthly-price, .quarterly-price, and .annual-price elements based on the selected period from the prices object.
Initialize First Toggle as Active
pricingToggles[0].classList.add('active', 'bg-white', 'text-text-primary', 'shadow-sm');
pricingToggles[0].classList.remove('text-text-secondary');
- Automatically activates the first pricing toggle when the page loads.
2. Bulk Content Toggle Functionality
const bulkToggles = document.querySelectorAll('.bulk-toggle');
bulkToggles.forEach((toggle) => {
toggle.addEventListener('click', () => {
const targetId = toggle.dataset.target;
const content = document.getElementById(targetId);
const arrow = toggle.querySelector('svg');
if (content.classList.contains('hidden')) {
content.classList.remove('hidden');
arrow.style.transform = 'rotate(180deg)';
} else {
content.classList.add('hidden');
arrow.style.transform = 'rotate(0deg)';
}
});
});
- bulkToggles: Selects all elements with .bulk-toggle.
- When clicked:
- Finds the content block (document.getElementById(targetId)) using the data-target attribute.
- If content is hidden, it shows it (content.classList.remove('hidden')) and rotates the arrow downwards (180°).
- If it's visible, it hides it and resets the arrow rotation.
3. Tooltip Functionality
const tooltipTriggers = document.querySelectorAll('.tooltip-trigger');
tooltipTriggers.forEach((trigger) => {
const tooltip = trigger.querySelector('.tooltip');
trigger.addEventListener('mouseenter', () => {
tooltip.classList.remove('hidden');
});
trigger.addEventListener('mouseleave', () => {
tooltip.classList.add('hidden');
});
});
- tooltipTriggers: Selects all elements with .tooltip-trigger.
- On mouse hover (mouseenter), it shows the tooltip by removing the hidden class.
- On mouse leave (mouseleave), it hides the tooltip by adding the hidden class.
// Pricing toggle functionality
const pricingToggles = document.querySelectorAll('.pricing-toggle');
const monthlyPrices = document.querySelectorAll('.monthly-price');
const quarterlyPrices = document.querySelectorAll('.quarterly-price');
const annualPrices = document.querySelectorAll('.annual-price');
const prices = {
monthly: { monthly: 49, quarterly: 124, annual: 441 },
quarterly: { monthly: 42, quarterly: 124, annual: 441 },
annual: { monthly: 37, quarterly: 111, annual: 441 },
};
pricingToggles.forEach((toggle) => {
toggle.addEventListener('click', () => {
// Remove active class from all toggles
pricingToggles.forEach((t) => {
t.classList.remove(
'active',
'bg-white',
'text-text-primary',
'shadow-sm'
);
t.classList.add('text-text-secondary');
});
// Add active class to clicked toggle
toggle.classList.add(
'active',
'bg-white',
'text-text-primary',
'shadow-sm'
);
toggle.classList.remove('text-text-secondary');
const period = toggle.dataset.period;
// Update prices based on selected period
monthlyPrices.forEach((price) => {
price.textContent = `$${prices[period].monthly}`;
});
quarterlyPrices.forEach((price) => {
price.textContent = `$${prices[period].quarterly}`;
});
annualPrices.forEach((price) => {
price.textContent = `$${prices[period].annual}`;
});
});
});
// Initialize first toggle as active
pricingToggles[0].classList.add(
'active',
'bg-white',
'text-text-primary',
'shadow-sm'
);
pricingToggles[0].classList.remove('text-text-secondary');
// Bulk content toggle functionality
const bulkToggles = document.querySelectorAll('.bulk-toggle');
bulkToggles.forEach((toggle) => {
toggle.addEventListener('click', () => {
const targetId = toggle.dataset.target;
const content = document.getElementById(targetId);
const arrow = toggle.querySelector('svg');
if (content.classList.contains('hidden')) {
content.classList.remove('hidden');
arrow.style.transform = 'rotate(180deg)';
} else {
content.classList.add('hidden');
arrow.style.transform = 'rotate(0deg)';
}
});
});
// Tooltip functionality
const tooltipTriggers = document.querySelectorAll('.tooltip-trigger');
tooltipTriggers.forEach((trigger) => {
const tooltip = trigger.querySelector('.tooltip');
trigger.addEventListener('mouseenter', () => {
tooltip.classList.remove('hidden');
});
trigger.addEventListener('mouseleave', () => {
tooltip.classList.add('hidden');
});
});Final Output:
Conclusion:
An advanced pricing table with feature comparison enhances user experience and makes pricing options clearer. By using HTML, CSS, and JavaScript, we created a modern, interactive, and responsive design with features like a toggle switch, highlighted plans, and feature icons.
You can further improve it by adding animations (GSAP), tooltips for features, and dark mode support.
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 😊


