18 / 18
All 18 in 18 Tailwind CSS Product Cards
- 01Tailwind Product Card with Hover-Reveal Add to Cart
- 02Pure-Tailwind 3D Flip Product Card with Spec Back
- 03Tailwind Contained Image Zoom Product Card
- 04Tailwind Mouse-Tracking 3D Tilt Product Card
- 05Tailwind Slide-Up Details Panel Product Card
- 06Tailwind Minimalist eCommerce Product Card
- 07Tailwind Glassmorphism Product Card
- 08Tailwind Dark-Mode Gadget Product Card with Accent Glow
- 09Tailwind Neon Glowing Conic-Border Product Card
- 10Tailwind Material-Design Elevation Product Card
- 11Tailwind Horizontal Product Card with Container Queries
- 12Tailwind Auto-Fit Product Catalog Grid
- 13Tailwind Sale Ribbon & Coupon Badge Product Card
- 14Tailwind Sneaker Card with Colourway & Size Picker
- 15Tailwind Digital Download Product Card
- 16Tailwind Food Delivery Card with Quantity Stepper
- 17Tailwind Real Estate Listing Card with Favourite Toggle
- 18Tailwind Subscription Pricing Card with Billing Switch
Tailwind Subscription Pricing Card with Billing Switch
The featured pricing tier: a 'Most Popular' banner, an accent border, a lifted card and a ticked feature list — plus the monthly/annual switch the CSS original left as a suggestion. Two sr-only radios drive it through group-has-[#tpc18-yr:checked]:, so both prices and the segmented control's own state are pure CSS.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tpc-18 w-full min-h-svh grid place-items-center bg-tpc18-bg font-tpc18-sans px-5 py-12" aria-label="Subscription pricing card demo">
<article class="group/bill relative w-[min(340px,100%)] px-7 pt-8 pb-7 rounded-[20px] bg-tpc18-card border-2 border-tpc18-acc -translate-y-1.5 shadow-[0_30px_60px_-28px_color-mix(in_oklch,var(--color-tpc18-acc)_55%,transparent)]">
<span class="absolute -top-3.25 left-7 px-3.5 py-1.5 rounded-full bg-tpc18-acc text-tpc18-onacc text-[11px] font-bold tracking-[0.06em] uppercase shadow-[0_8px_18px_-8px_var(--color-tpc18-acc)]">Most Popular</span>
<input type="radio" name="tpc18-bill" id="tpc18-mo" class="sr-only" checked>
<input type="radio" name="tpc18-bill" id="tpc18-yr" class="sr-only">
<p class="text-[12px] font-medium tracking-[0.2em] uppercase text-tpc18-acc">Pro</p>
<div role="group" aria-label="Billing interval" class="mt-3.5 inline-flex p-1 rounded-full bg-tpc18-seg">
<label for="tpc18-mo" class="grid place-items-center min-h-11 px-4.5 rounded-full cursor-pointer text-[13px] font-semibold text-tpc18-mut transition-colors duration-200 group-has-[#tpc18-mo:checked]/bill:bg-tpc18-card group-has-[#tpc18-mo:checked]/bill:text-tpc18-ink group-has-[#tpc18-mo:checked]/bill:shadow-[0_2px_8px_-3px_oklch(0.2_0.04_290/0.3)] motion-reduce:transition-none">Monthly</label>
<label for="tpc18-yr" class="grid place-items-center min-h-11 px-4.5 rounded-full cursor-pointer text-[13px] font-semibold text-tpc18-mut transition-colors duration-200 group-has-[#tpc18-yr:checked]/bill:bg-tpc18-card group-has-[#tpc18-yr:checked]/bill:text-tpc18-ink group-has-[#tpc18-yr:checked]/bill:shadow-[0_2px_8px_-3px_oklch(0.2_0.04_290/0.3)] motion-reduce:transition-none">Annual</label>
</div>
<p class="mt-4 flex items-baseline gap-1">
<span class="text-[52px] font-bold leading-none tracking-[-0.02em] text-tpc18-ink tabular-nums group-has-[#tpc18-yr:checked]/bill:hidden">$24</span>
<span class="hidden text-[52px] font-bold leading-none tracking-[-0.02em] text-tpc18-ink tabular-nums group-has-[#tpc18-yr:checked]/bill:inline">$19</span>
<span class="text-[17px] font-semibold text-tpc18-mut">/mo</span>
</p>
<p class="mt-2 text-[13px] text-tpc18-mut">
<span class="group-has-[#tpc18-yr:checked]/bill:hidden">billed monthly · cancel anytime</span>
<span class="hidden group-has-[#tpc18-yr:checked]/bill:inline">billed annually · save 20%</span>
</p>
<ul class="mt-5.5 mb-6 flex flex-col gap-3 list-none">
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Unlimited projects</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Advanced analytics</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Priority support</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Team roles & permissions</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">API access</li>
</ul>
<div class="mb-4.5 flex items-center gap-3">
<ul class="flex list-none pl-0" aria-hidden="true">
<li class="size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
<li class="-ml-2.5 size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
<li class="-ml-2.5 size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
</ul>
<p class="text-[12.5px] leading-[1.35] text-tpc18-mut">Trusted by <b class="font-semibold text-tpc18-body tabular-nums">2,400+</b> product teams</p>
</div>
<button type="button" class="w-full min-h-11 h-12.5 rounded-[13px] border-0 cursor-pointer bg-tpc18-acc text-tpc18-onacc text-[15px] font-bold shadow-[0_12px_26px_-12px_var(--color-tpc18-acc)] transition-colors duration-150 hover:bg-tpc18-acc2 focus-visible:outline-3 focus-visible:outline-tpc18-ring focus-visible:outline-offset-2 motion-reduce:transition-none">Start free trial</button>
<p class="mt-3 text-center text-[12px] text-tpc18-mut">14-day trial · no card required</p>
</article>
</section><section class="tpc-18 w-full min-h-svh grid place-items-center bg-tpc18-bg font-tpc18-sans px-5 py-12" aria-label="Subscription pricing card demo">
<article class="group/bill relative w-[min(340px,100%)] px-7 pt-8 pb-7 rounded-[20px] bg-tpc18-card border-2 border-tpc18-acc -translate-y-1.5 shadow-[0_30px_60px_-28px_color-mix(in_oklch,var(--color-tpc18-acc)_55%,transparent)]">
<span class="absolute -top-3.25 left-7 px-3.5 py-1.5 rounded-full bg-tpc18-acc text-tpc18-onacc text-[11px] font-bold tracking-[0.06em] uppercase shadow-[0_8px_18px_-8px_var(--color-tpc18-acc)]">Most Popular</span>
<input type="radio" name="tpc18-bill" id="tpc18-mo" class="sr-only" checked>
<input type="radio" name="tpc18-bill" id="tpc18-yr" class="sr-only">
<p class="text-[12px] font-medium tracking-[0.2em] uppercase text-tpc18-acc">Pro</p>
<div role="group" aria-label="Billing interval" class="mt-3.5 inline-flex p-1 rounded-full bg-tpc18-seg">
<label for="tpc18-mo" class="grid place-items-center min-h-11 px-4.5 rounded-full cursor-pointer text-[13px] font-semibold text-tpc18-mut transition-colors duration-200 group-has-[#tpc18-mo:checked]/bill:bg-tpc18-card group-has-[#tpc18-mo:checked]/bill:text-tpc18-ink group-has-[#tpc18-mo:checked]/bill:shadow-[0_2px_8px_-3px_oklch(0.2_0.04_290/0.3)] motion-reduce:transition-none">Monthly</label>
<label for="tpc18-yr" class="grid place-items-center min-h-11 px-4.5 rounded-full cursor-pointer text-[13px] font-semibold text-tpc18-mut transition-colors duration-200 group-has-[#tpc18-yr:checked]/bill:bg-tpc18-card group-has-[#tpc18-yr:checked]/bill:text-tpc18-ink group-has-[#tpc18-yr:checked]/bill:shadow-[0_2px_8px_-3px_oklch(0.2_0.04_290/0.3)] motion-reduce:transition-none">Annual</label>
</div>
<p class="mt-4 flex items-baseline gap-1">
<span class="text-[52px] font-bold leading-none tracking-[-0.02em] text-tpc18-ink tabular-nums group-has-[#tpc18-yr:checked]/bill:hidden">$24</span>
<span class="hidden text-[52px] font-bold leading-none tracking-[-0.02em] text-tpc18-ink tabular-nums group-has-[#tpc18-yr:checked]/bill:inline">$19</span>
<span class="text-[17px] font-semibold text-tpc18-mut">/mo</span>
</p>
<p class="mt-2 text-[13px] text-tpc18-mut">
<span class="group-has-[#tpc18-yr:checked]/bill:hidden">billed monthly · cancel anytime</span>
<span class="hidden group-has-[#tpc18-yr:checked]/bill:inline">billed annually · save 20%</span>
</p>
<ul class="mt-5.5 mb-6 flex flex-col gap-3 list-none">
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Unlimited projects</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Advanced analytics</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Priority support</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Team roles & permissions</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">API access</li>
</ul>
<div class="mb-4.5 flex items-center gap-3">
<ul class="flex list-none pl-0" aria-hidden="true">
<li class="size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
<li class="-ml-2.5 size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
<li class="-ml-2.5 size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
</ul>
<p class="text-[12.5px] leading-[1.35] text-tpc18-mut">Trusted by <b class="font-semibold text-tpc18-body tabular-nums">2,400+</b> product teams</p>
</div>
<button type="button" class="w-full min-h-11 h-12.5 rounded-[13px] border-0 cursor-pointer bg-tpc18-acc text-tpc18-onacc text-[15px] font-bold shadow-[0_12px_26px_-12px_var(--color-tpc18-acc)] transition-colors duration-150 hover:bg-tpc18-acc2 focus-visible:outline-3 focus-visible:outline-tpc18-ring focus-visible:outline-offset-2 motion-reduce:transition-none">Start free trial</button>
<p class="mt-3 text-center text-[12px] text-tpc18-mut">14-day trial · no card required</p>
</article>
</section>@import "tailwindcss";
@theme {
--color-tpc18-bg: #f1f0f7;
--color-tpc18-bg: oklch(0.95 0.008 290);
--color-tpc18-card: #ffffff;
--color-tpc18-card: oklch(1 0 0);
--color-tpc18-ink: #1a1430;
--color-tpc18-ink: oklch(0.23 0.04 295);
--color-tpc18-body: #3a3355;
--color-tpc18-body: oklch(0.36 0.04 295);
--color-tpc18-mut: #5f5880;
--color-tpc18-mut: oklch(0.48 0.04 295);
--color-tpc18-seg: #efedf7;
--color-tpc18-seg: oklch(0.95 0.012 295);
--color-tpc18-acc: #5b3fc4;
--color-tpc18-acc: oklch(0.48 0.19 292);
--color-tpc18-acc2: #4c34a6;
--color-tpc18-acc2: oklch(0.42 0.18 292);
--color-tpc18-ring: #bda6f0;
--color-tpc18-ring: oklch(0.78 0.11 292);
--color-tpc18-onacc: #ffffff;
--color-tpc18-onacc: oklch(1 0 0);
--font-tpc18-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tpc-18 {
width: 100%;
min-height: 100svh;
}
.tpc-18 ::selection {
background: var(--color-tpc18-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tpc18-bg: #f1f0f7;
--color-tpc18-bg: oklch(0.95 0.008 290);
--color-tpc18-card: #ffffff;
--color-tpc18-card: oklch(1 0 0);
--color-tpc18-ink: #1a1430;
--color-tpc18-ink: oklch(0.23 0.04 295);
--color-tpc18-body: #3a3355;
--color-tpc18-body: oklch(0.36 0.04 295);
--color-tpc18-mut: #5f5880;
--color-tpc18-mut: oklch(0.48 0.04 295);
--color-tpc18-seg: #efedf7;
--color-tpc18-seg: oklch(0.95 0.012 295);
--color-tpc18-acc: #5b3fc4;
--color-tpc18-acc: oklch(0.48 0.19 292);
--color-tpc18-acc2: #4c34a6;
--color-tpc18-acc2: oklch(0.42 0.18 292);
--color-tpc18-ring: #bda6f0;
--color-tpc18-ring: oklch(0.78 0.11 292);
--color-tpc18-onacc: #ffffff;
--color-tpc18-onacc: oklch(1 0 0);
--font-tpc18-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tpc-18 {
width: 100%;
min-height: 100svh;
}
.tpc-18 ::selection {
background: var(--color-tpc18-acc);
color: #fff;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tpc18-bg: #f1f0f7;
--color-tpc18-bg: oklch(0.95 0.008 290);
--color-tpc18-card: #ffffff;
--color-tpc18-card: oklch(1 0 0);
--color-tpc18-ink: #1a1430;
--color-tpc18-ink: oklch(0.23 0.04 295);
--color-tpc18-body: #3a3355;
--color-tpc18-body: oklch(0.36 0.04 295);
--color-tpc18-mut: #5f5880;
--color-tpc18-mut: oklch(0.48 0.04 295);
--color-tpc18-seg: #efedf7;
--color-tpc18-seg: oklch(0.95 0.012 295);
--color-tpc18-acc: #5b3fc4;
--color-tpc18-acc: oklch(0.48 0.19 292);
--color-tpc18-acc2: #4c34a6;
--color-tpc18-acc2: oklch(0.42 0.18 292);
--color-tpc18-ring: #bda6f0;
--color-tpc18-ring: oklch(0.78 0.11 292);
--color-tpc18-onacc: #ffffff;
--color-tpc18-onacc: oklch(1 0 0);
--font-tpc18-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tpc-18 {
width: 100%;
min-height: 100svh;
}
.tpc-18 ::selection {
background: var(--color-tpc18-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tpc18-bg: #f1f0f7;
--color-tpc18-bg: oklch(0.95 0.008 290);
--color-tpc18-card: #ffffff;
--color-tpc18-card: oklch(1 0 0);
--color-tpc18-ink: #1a1430;
--color-tpc18-ink: oklch(0.23 0.04 295);
--color-tpc18-body: #3a3355;
--color-tpc18-body: oklch(0.36 0.04 295);
--color-tpc18-mut: #5f5880;
--color-tpc18-mut: oklch(0.48 0.04 295);
--color-tpc18-seg: #efedf7;
--color-tpc18-seg: oklch(0.95 0.012 295);
--color-tpc18-acc: #5b3fc4;
--color-tpc18-acc: oklch(0.48 0.19 292);
--color-tpc18-acc2: #4c34a6;
--color-tpc18-acc2: oklch(0.42 0.18 292);
--color-tpc18-ring: #bda6f0;
--color-tpc18-ring: oklch(0.78 0.11 292);
--color-tpc18-onacc: #ffffff;
--color-tpc18-onacc: oklch(1 0 0);
--font-tpc18-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tpc-18 {
width: 100%;
min-height: 100svh;
}
.tpc-18 ::selection {
background: var(--color-tpc18-acc);
color: #fff;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The port that gained a feature. The CSS original listed a monthly/annual switch as a customisation;
group-has-[…]:makes it cheap enough to ship, because one ancestor-state question drives four unrelated elements with no script and no duplicated card. group/billis named on purpose. Pricing cards sit in rows, and rows get their owngroup— an unnamedgroup-has-[]would bind to whichever ancestor happened to be closest.before:content-['✓']keeps the tick as a pseudo-element (correct: it is decoration) while keeping its declaration in the class list. This is the one place where the utility version is genuinely longer than the CSS, and still the better trade, since adding a feature row needs no stylesheet edit.- The featured modifier class dissolved.
.cpc-18__card--featuredwas three declarations; here they are three utilities on the card, and a standard tier is the same markup minus those three. - Enhancement over the CSS original: the billing switch, a 'save 20%' note,
tabular-numson the price so the figure does not shift width when switched, and body text darkened to clear 4.5:1 against white.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Subscription Pricing Card with Billing Switch
Source: https://codefronts.com/components/tailwind-css-product-cards/tailwind-subscription-pricing-card-billing-switch/
The featured pricing tier: a 'Most Popular' banner, an accent border, a lifted card and a ticked feature list — plus the monthly/annual switch the CSS original left as a suggestion. Two sr-only radios drive it through group-has-[#tpc18-yr:checked]:, so both prices and the segmented control's own state are pure CSS.
## HTML
```html
<section class="tpc-18 w-full min-h-svh grid place-items-center bg-tpc18-bg font-tpc18-sans px-5 py-12" aria-label="Subscription pricing card demo">
<article class="group/bill relative w-[min(340px,100%)] px-7 pt-8 pb-7 rounded-[20px] bg-tpc18-card border-2 border-tpc18-acc -translate-y-1.5 shadow-[0_30px_60px_-28px_color-mix(in_oklch,var(--color-tpc18-acc)_55%,transparent)]">
<span class="absolute -top-3.25 left-7 px-3.5 py-1.5 rounded-full bg-tpc18-acc text-tpc18-onacc text-[11px] font-bold tracking-[0.06em] uppercase shadow-[0_8px_18px_-8px_var(--color-tpc18-acc)]">Most Popular</span>
<input type="radio" name="tpc18-bill" id="tpc18-mo" class="sr-only" checked>
<input type="radio" name="tpc18-bill" id="tpc18-yr" class="sr-only">
<p class="text-[12px] font-medium tracking-[0.2em] uppercase text-tpc18-acc">Pro</p>
<div role="group" aria-label="Billing interval" class="mt-3.5 inline-flex p-1 rounded-full bg-tpc18-seg">
<label for="tpc18-mo" class="grid place-items-center min-h-11 px-4.5 rounded-full cursor-pointer text-[13px] font-semibold text-tpc18-mut transition-colors duration-200 group-has-[#tpc18-mo:checked]/bill:bg-tpc18-card group-has-[#tpc18-mo:checked]/bill:text-tpc18-ink group-has-[#tpc18-mo:checked]/bill:shadow-[0_2px_8px_-3px_oklch(0.2_0.04_290/0.3)] motion-reduce:transition-none">Monthly</label>
<label for="tpc18-yr" class="grid place-items-center min-h-11 px-4.5 rounded-full cursor-pointer text-[13px] font-semibold text-tpc18-mut transition-colors duration-200 group-has-[#tpc18-yr:checked]/bill:bg-tpc18-card group-has-[#tpc18-yr:checked]/bill:text-tpc18-ink group-has-[#tpc18-yr:checked]/bill:shadow-[0_2px_8px_-3px_oklch(0.2_0.04_290/0.3)] motion-reduce:transition-none">Annual</label>
</div>
<p class="mt-4 flex items-baseline gap-1">
<span class="text-[52px] font-bold leading-none tracking-[-0.02em] text-tpc18-ink tabular-nums group-has-[#tpc18-yr:checked]/bill:hidden">$24</span>
<span class="hidden text-[52px] font-bold leading-none tracking-[-0.02em] text-tpc18-ink tabular-nums group-has-[#tpc18-yr:checked]/bill:inline">$19</span>
<span class="text-[17px] font-semibold text-tpc18-mut">/mo</span>
</p>
<p class="mt-2 text-[13px] text-tpc18-mut">
<span class="group-has-[#tpc18-yr:checked]/bill:hidden">billed monthly · cancel anytime</span>
<span class="hidden group-has-[#tpc18-yr:checked]/bill:inline">billed annually · save 20%</span>
</p>
<ul class="mt-5.5 mb-6 flex flex-col gap-3 list-none">
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Unlimited projects</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Advanced analytics</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Priority support</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Team roles & permissions</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">API access</li>
</ul>
<div class="mb-4.5 flex items-center gap-3">
<ul class="flex list-none pl-0" aria-hidden="true">
<li class="size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
<li class="-ml-2.5 size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
<li class="-ml-2.5 size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
</ul>
<p class="text-[12.5px] leading-[1.35] text-tpc18-mut">Trusted by <b class="font-semibold text-tpc18-body tabular-nums">2,400+</b> product teams</p>
</div>
<button type="button" class="w-full min-h-11 h-12.5 rounded-[13px] border-0 cursor-pointer bg-tpc18-acc text-tpc18-onacc text-[15px] font-bold shadow-[0_12px_26px_-12px_var(--color-tpc18-acc)] transition-colors duration-150 hover:bg-tpc18-acc2 focus-visible:outline-3 focus-visible:outline-tpc18-ring focus-visible:outline-offset-2 motion-reduce:transition-none">Start free trial</button>
<p class="mt-3 text-center text-[12px] text-tpc18-mut">14-day trial · no card required</p>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpc18-bg: #f1f0f7;
--color-tpc18-bg: oklch(0.95 0.008 290);
--color-tpc18-card: #ffffff;
--color-tpc18-card: oklch(1 0 0);
--color-tpc18-ink: #1a1430;
--color-tpc18-ink: oklch(0.23 0.04 295);
--color-tpc18-body: #3a3355;
--color-tpc18-body: oklch(0.36 0.04 295);
--color-tpc18-mut: #5f5880;
--color-tpc18-mut: oklch(0.48 0.04 295);
--color-tpc18-seg: #efedf7;
--color-tpc18-seg: oklch(0.95 0.012 295);
--color-tpc18-acc: #5b3fc4;
--color-tpc18-acc: oklch(0.48 0.19 292);
--color-tpc18-acc2: #4c34a6;
--color-tpc18-acc2: oklch(0.42 0.18 292);
--color-tpc18-ring: #bda6f0;
--color-tpc18-ring: oklch(0.78 0.11 292);
--color-tpc18-onacc: #ffffff;
--color-tpc18-onacc: oklch(1 0 0);
--font-tpc18-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tpc-18 {
width: 100%;
min-height: 100svh;
}
.tpc-18 ::selection {
background: var(--color-tpc18-acc);
color: #fff;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Subscription Pricing Card with Billing Switch
Source: https://codefronts.com/components/tailwind-css-product-cards/tailwind-subscription-pricing-card-billing-switch/
The featured pricing tier: a 'Most Popular' banner, an accent border, a lifted card and a ticked feature list — plus the monthly/annual switch the CSS original left as a suggestion. Two sr-only radios drive it through group-has-[#tpc18-yr:checked]:, so both prices and the segmented control's own state are pure CSS.
## HTML
```html
<section class="tpc-18 w-full min-h-svh grid place-items-center bg-tpc18-bg font-tpc18-sans px-5 py-12" aria-label="Subscription pricing card demo">
<article class="group/bill relative w-[min(340px,100%)] px-7 pt-8 pb-7 rounded-[20px] bg-tpc18-card border-2 border-tpc18-acc -translate-y-1.5 shadow-[0_30px_60px_-28px_color-mix(in_oklch,var(--color-tpc18-acc)_55%,transparent)]">
<span class="absolute -top-3.25 left-7 px-3.5 py-1.5 rounded-full bg-tpc18-acc text-tpc18-onacc text-[11px] font-bold tracking-[0.06em] uppercase shadow-[0_8px_18px_-8px_var(--color-tpc18-acc)]">Most Popular</span>
<input type="radio" name="tpc18-bill" id="tpc18-mo" class="sr-only" checked>
<input type="radio" name="tpc18-bill" id="tpc18-yr" class="sr-only">
<p class="text-[12px] font-medium tracking-[0.2em] uppercase text-tpc18-acc">Pro</p>
<div role="group" aria-label="Billing interval" class="mt-3.5 inline-flex p-1 rounded-full bg-tpc18-seg">
<label for="tpc18-mo" class="grid place-items-center min-h-11 px-4.5 rounded-full cursor-pointer text-[13px] font-semibold text-tpc18-mut transition-colors duration-200 group-has-[#tpc18-mo:checked]/bill:bg-tpc18-card group-has-[#tpc18-mo:checked]/bill:text-tpc18-ink group-has-[#tpc18-mo:checked]/bill:shadow-[0_2px_8px_-3px_oklch(0.2_0.04_290/0.3)] motion-reduce:transition-none">Monthly</label>
<label for="tpc18-yr" class="grid place-items-center min-h-11 px-4.5 rounded-full cursor-pointer text-[13px] font-semibold text-tpc18-mut transition-colors duration-200 group-has-[#tpc18-yr:checked]/bill:bg-tpc18-card group-has-[#tpc18-yr:checked]/bill:text-tpc18-ink group-has-[#tpc18-yr:checked]/bill:shadow-[0_2px_8px_-3px_oklch(0.2_0.04_290/0.3)] motion-reduce:transition-none">Annual</label>
</div>
<p class="mt-4 flex items-baseline gap-1">
<span class="text-[52px] font-bold leading-none tracking-[-0.02em] text-tpc18-ink tabular-nums group-has-[#tpc18-yr:checked]/bill:hidden">$24</span>
<span class="hidden text-[52px] font-bold leading-none tracking-[-0.02em] text-tpc18-ink tabular-nums group-has-[#tpc18-yr:checked]/bill:inline">$19</span>
<span class="text-[17px] font-semibold text-tpc18-mut">/mo</span>
</p>
<p class="mt-2 text-[13px] text-tpc18-mut">
<span class="group-has-[#tpc18-yr:checked]/bill:hidden">billed monthly · cancel anytime</span>
<span class="hidden group-has-[#tpc18-yr:checked]/bill:inline">billed annually · save 20%</span>
</p>
<ul class="mt-5.5 mb-6 flex flex-col gap-3 list-none">
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Unlimited projects</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Advanced analytics</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Priority support</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">Team roles & permissions</li>
<li class="relative pl-7.5 text-[14.5px] leading-[1.4] text-tpc18-body before:content-['✓'] before:absolute before:left-0 before:top-0 before:size-5 before:rounded-full before:grid before:place-items-center before:text-[12px] before:font-extrabold before:text-tpc18-onacc before:bg-tpc18-acc">API access</li>
</ul>
<div class="mb-4.5 flex items-center gap-3">
<ul class="flex list-none pl-0" aria-hidden="true">
<li class="size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
<li class="-ml-2.5 size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
<li class="-ml-2.5 size-8 rounded-full bg-tpc18-seg bg-[url('https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=120&h=120&fit=crop&q=70')] bg-cover bg-center shadow-[0_0_0_2px_var(--color-tpc18-card)]"></li>
</ul>
<p class="text-[12.5px] leading-[1.35] text-tpc18-mut">Trusted by <b class="font-semibold text-tpc18-body tabular-nums">2,400+</b> product teams</p>
</div>
<button type="button" class="w-full min-h-11 h-12.5 rounded-[13px] border-0 cursor-pointer bg-tpc18-acc text-tpc18-onacc text-[15px] font-bold shadow-[0_12px_26px_-12px_var(--color-tpc18-acc)] transition-colors duration-150 hover:bg-tpc18-acc2 focus-visible:outline-3 focus-visible:outline-tpc18-ring focus-visible:outline-offset-2 motion-reduce:transition-none">Start free trial</button>
<p class="mt-3 text-center text-[12px] text-tpc18-mut">14-day trial · no card required</p>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpc18-bg: #f1f0f7;
--color-tpc18-bg: oklch(0.95 0.008 290);
--color-tpc18-card: #ffffff;
--color-tpc18-card: oklch(1 0 0);
--color-tpc18-ink: #1a1430;
--color-tpc18-ink: oklch(0.23 0.04 295);
--color-tpc18-body: #3a3355;
--color-tpc18-body: oklch(0.36 0.04 295);
--color-tpc18-mut: #5f5880;
--color-tpc18-mut: oklch(0.48 0.04 295);
--color-tpc18-seg: #efedf7;
--color-tpc18-seg: oklch(0.95 0.012 295);
--color-tpc18-acc: #5b3fc4;
--color-tpc18-acc: oklch(0.48 0.19 292);
--color-tpc18-acc2: #4c34a6;
--color-tpc18-acc2: oklch(0.42 0.18 292);
--color-tpc18-ring: #bda6f0;
--color-tpc18-ring: oklch(0.78 0.11 292);
--color-tpc18-onacc: #ffffff;
--color-tpc18-onacc: oklch(1 0 0);
--font-tpc18-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tpc-18 {
width: 100%;
min-height: 100svh;
}
.tpc-18 ::selection {
background: var(--color-tpc18-acc);
color: #fff;
}
```How this works
The toggle is an ancestor-state problem. The radios live in the segmented control; the price lives 40 lines away. peer-* cannot reach across that, so the card becomes a named group and every dependent element asks the group a question:
<article class="group/bill …">
<input id="tpc18-yr" type="radio" class="sr-only">
<span class="group-has-[#tpc18-yr:checked]/bill:hidden">$24</span>
<span class="hidden group-has-[#tpc18-yr:checked]/bill:inline">$19</span>The same variant styles the active segment of the control, swaps the interval suffix and swaps the billing note — four different elements, one question, no script.
The tick is a pseudo-element, and that is fine. before:content-['✓'] plus six positioning utilities beats a wrapper span per feature: the tick is decoration, so it belongs in CSS rather than the DOM, and v4's before: variant keeps it in the class list anyway.
Featured is marked three ways. Banner, accent border and a -translate-y-1.5 lift. In a row of three tiers, any one of those alone is easy to miss.
Make it yours
- Featured or not: the banner,
border-2 border-tpc18-accand-translate-y-1.5are the whole treatment. Remove all three for a standard tier. - Accent:
--color-tpc18-accdrives ticks, banner, border, CTA, both shadows and the active segment. - Prices: the two spans per figure. Add a 'Save 20%' pill beside the annual segment.
- Feature list: add
<li>items; the tick recipe is per-item and needs no extra markup. - Drop the switch entirely and keep a single price if your plans are monthly-only.
Gotchas — read before shipping
- The price must be real text. An image price is invisible to search and to AT, and cannot be switched by a CSS toggle.
- Name the group.
group-has-[…]/bill:is explicit; an unnamedgroup-has-[…]binds to the nearestgroupancestor, which breaks the moment these cards sit inside a grouped pricing row. - The 'Most Popular' banner is emphasis, not information — don't let it be the only thing saying which plan is recommended.
- Feature ticks need real text beside them; a column of bare checkmarks tells AT nothing.
- The switch is two radios with a shared
nameinside a labelledrole="group". Two independent checkboxes would allow both or neither. :has()is the floor. Without it the card renders the monthly price and a static control — usable, but confirm that default is the one you want shown.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:has() requires Chrome 105+, Safari 15.4+, Firefox 121+, and color-mix Chrome 111+/Safari 16.2+. Without :has() the card shows its monthly price and the switch stops responding; every other part of the tier renders normally.