Pure TailwindMIT licensed

Tailwind Currency Switcher Pricing Section

Three currencies, nine prices, zero JavaScript: hidden radios plus group-has-[…]: swap every visible price across all three tiers at once. Burgundy on cream, with the featured tier inverted — and the port's clean-up is the inverted card, whose eight descendant override rules become ordinary utilities on the elements themselves.

Live Demo
Plain CSSTailwind v4
Try it

Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49

Cruip · Affiliate link

Browse Cruip templates

The code

<section class="tps-17 group w-full min-h-svh bg-tps17-paper font-tps17-sans text-tps17-ink px-[clamp(20px,4vw,40px)] py-[clamp(28px,5vw,56px)]" aria-label="Pricing">
  <header class="text-center mb-[clamp(24px,4vw,36px)]">
    <h2 class="m-0 mb-2 font-tps17-serif text-[clamp(26px,4vw,38px)] font-bold italic tracking-[-0.015em] text-tps17-wine">Pricing in your currency.</h2>
    <p class="m-0 mb-5 text-sm text-tps17-muted">Pay where you live, with the rate fixed at billing time.</p>
    <fieldset class="relative inline-grid grid-cols-3 m-0 p-0.75 rounded-full bg-white border-[1.5px] border-tps17-wine">
      <legend class="sr-only">Currency</legend>
      <input type="radio" name="tps-17__c" id="tps-17-usd" class="peer/usd sr-only" checked>
      <label for="tps-17-usd" class="col-start-1 row-start-1 px-[22px] py-1.75 rounded-full font-tps17-mono text-xs font-bold tracking-[0.1em] text-tps17-muted cursor-pointer transition-[background-color,color] duration-[180ms] peer-checked/usd:bg-tps17-wine peer-checked/usd:text-tps17-cream peer-focus-visible/usd:outline-2 peer-focus-visible/usd:outline-tps17-wine peer-focus-visible/usd:outline-offset-[3px] motion-reduce:transition-none">USD</label>
      <input type="radio" name="tps-17__c" id="tps-17-eur" class="peer/eur sr-only">
      <label for="tps-17-eur" class="col-start-2 row-start-1 px-[22px] py-1.75 rounded-full font-tps17-mono text-xs font-bold tracking-[0.1em] text-tps17-muted cursor-pointer transition-[background-color,color] duration-[180ms] peer-checked/eur:bg-tps17-wine peer-checked/eur:text-tps17-cream peer-focus-visible/eur:outline-2 peer-focus-visible/eur:outline-tps17-wine peer-focus-visible/eur:outline-offset-[3px] motion-reduce:transition-none">EUR</label>
      <input type="radio" name="tps-17__c" id="tps-17-gbp" class="peer/gbp sr-only">
      <label for="tps-17-gbp" class="col-start-3 row-start-1 px-[22px] py-1.75 rounded-full font-tps17-mono text-xs font-bold tracking-[0.1em] text-tps17-muted cursor-pointer transition-[background-color,color] duration-[180ms] peer-checked/gbp:bg-tps17-wine peer-checked/gbp:text-tps17-cream peer-focus-visible/gbp:outline-2 peer-focus-visible/gbp:outline-tps17-wine peer-focus-visible/gbp:outline-offset-[3px] motion-reduce:transition-none">GBP</label>
    </fieldset>
  </header>
  <div class="grid grid-cols-1 md:grid-cols-[repeat(3,minmax(0,1fr))] items-center gap-[18px] max-w-[380px] md:max-w-[1000px] mx-auto">
    <article class="relative flex flex-col px-[22px] pt-[26px] pb-[22px] rounded-[8px] bg-white border border-tps17-line">
      <h3 class="m-0 mb-3 font-tps17-serif text-[22px] font-bold italic text-tps17-wine">Petit</h3>
      <div class="flex items-baseline gap-1 mb-[18px] pb-[18px] border-b border-dashed border-tps17-line text-[38px] font-extrabold tracking-[-0.03em]">
        <span class="hidden group-has-[#tps-17-usd:checked]:inline">$12</span><span class="hidden group-has-[#tps-17-eur:checked]:inline">&euro;11</span><span class="hidden group-has-[#tps-17-gbp:checked]:inline">&pound;10</span>
        <em class="not-italic ml-1.5 text-[13px] font-medium text-tps17-muted">/month</em>
      </div>
      <ul class="list-none p-0 m-0 mb-5 flex-1 text-[13.5px]/[1.95] text-tps17-body">
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">Solo cellar</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">3 bottles tracked</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">Email reminders</li>
      </ul>
      <a href="#petit" class="block text-center px-4 py-2.75 rounded-[6px] bg-transparent text-tps17-wine border-[1.5px] border-current text-[13px] font-semibold uppercase tracking-[0.04em] no-underline transition-[background-color,color] duration-[180ms] hover:bg-tps17-wine hover:text-tps17-cream focus-visible:outline-2 focus-visible:outline-current focus-visible:outline-offset-[3px] motion-reduce:transition-none">Choose Petit</a>
    </article>
    <article class="relative flex flex-col px-[22px] pt-[26px] pb-[22px] rounded-[8px] bg-tps17-wine text-tps17-cream md:scale-[1.04] shadow-[0_18px_44px_--alpha(--color-tps17-wine/30)]">
      <span class="absolute -top-3 left-1/2 -translate-x-1/2 whitespace-nowrap px-2.75 py-1 rounded-full bg-tps17-paper text-tps17-wine border-[1.5px] border-tps17-wine font-tps17-mono text-[10.5px] font-bold uppercase tracking-[0.12em]">Sommelier&rsquo;s pick</span>
      <h3 class="m-0 mb-3 font-tps17-serif text-[22px] font-bold italic text-tps17-cream">Reserve</h3>
      <div class="flex items-baseline gap-1 mb-[18px] pb-[18px] border-b border-dashed border-tps17-cream/25 text-[38px] font-extrabold tracking-[-0.03em]">
        <span class="hidden group-has-[#tps-17-usd:checked]:inline">$36</span><span class="hidden group-has-[#tps-17-eur:checked]:inline">&euro;33</span><span class="hidden group-has-[#tps-17-gbp:checked]:inline">&pound;28</span>
        <em class="not-italic ml-1.5 text-[13px] font-medium text-tps17-cream/70">/month</em>
      </div>
      <ul class="list-none p-0 m-0 mb-5 flex-1 text-[13.5px]/[1.95] text-tps17-cream">
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-cream">Full cellar</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-cream">Unlimited bottles</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-cream">Tasting notes</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-cream">Pairing engine</li>
      </ul>
      <a href="#reserve" class="block text-center px-4 py-2.75 rounded-[6px] bg-tps17-cream text-tps17-wine border-[1.5px] border-tps17-cream text-[13px] font-semibold uppercase tracking-[0.04em] no-underline transition-[background-color,color] duration-[180ms] hover:bg-transparent hover:text-tps17-cream focus-visible:outline-2 focus-visible:outline-tps17-cream focus-visible:outline-offset-[3px] motion-reduce:transition-none">Choose Reserve</a>
    </article>
    <article class="relative flex flex-col px-[22px] pt-[26px] pb-[22px] rounded-[8px] bg-white border border-tps17-line">
      <h3 class="m-0 mb-3 font-tps17-serif text-[22px] font-bold italic text-tps17-wine">Grand Cru</h3>
      <div class="flex items-baseline gap-1 mb-[18px] pb-[18px] border-b border-dashed border-tps17-line text-[38px] font-extrabold tracking-[-0.03em]">
        <span class="hidden group-has-[#tps-17-usd:checked]:inline">$96</span><span class="hidden group-has-[#tps-17-eur:checked]:inline">&euro;88</span><span class="hidden group-has-[#tps-17-gbp:checked]:inline">&pound;76</span>
        <em class="not-italic ml-1.5 text-[13px] font-medium text-tps17-muted">/month</em>
      </div>
      <ul class="list-none p-0 m-0 mb-5 flex-1 text-[13.5px]/[1.95] text-tps17-body">
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">Multi-cellar</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">Insurance valuation</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">White-glove setup</li>
      </ul>
      <a href="#grand" class="block text-center px-4 py-2.75 rounded-[6px] bg-transparent text-tps17-wine border-[1.5px] border-current text-[13px] font-semibold uppercase tracking-[0.04em] no-underline transition-[background-color,color] duration-[180ms] hover:bg-tps17-wine hover:text-tps17-cream focus-visible:outline-2 focus-visible:outline-current focus-visible:outline-offset-[3px] motion-reduce:transition-none">Talk to us</a>
    </article>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tps17-paper: #faf6ee;
  --color-tps17-paper: oklch(0.97 0.012 85);
  --color-tps17-cream: #faf6ee;
  --color-tps17-cream: oklch(0.97 0.012 85);
  --color-tps17-ink: #2a0a14;
  --color-tps17-ink: oklch(0.21 0.06 10);
  --color-tps17-wine: #6b1224;
  --color-tps17-wine: oklch(0.34 0.13 15);
  --color-tps17-body: #4a2030;
  --color-tps17-body: oklch(0.32 0.07 8);
  --color-tps17-muted: #8a4555;
  --color-tps17-muted: oklch(0.48 0.09 10);
  --color-tps17-line: #e7d8c8;
  --color-tps17-line: oklch(0.89 0.025 75);
  --font-tps17-serif: 'Playfair Display', Georgia, serif;
  --font-tps17-mono: 'JetBrains Mono', monospace;
  --font-tps17-sans: 'Public Sans', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
   w-full min-h-svh, so the section is sized correctly in the moment before the
   browser build has compiled its utilities. */

.tps-17 {
  width: 100%;
  min-height: 100svh;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tps17-paper: #faf6ee;
  --color-tps17-paper: oklch(0.97 0.012 85);
  --color-tps17-cream: #faf6ee;
  --color-tps17-cream: oklch(0.97 0.012 85);
  --color-tps17-ink: #2a0a14;
  --color-tps17-ink: oklch(0.21 0.06 10);
  --color-tps17-wine: #6b1224;
  --color-tps17-wine: oklch(0.34 0.13 15);
  --color-tps17-body: #4a2030;
  --color-tps17-body: oklch(0.32 0.07 8);
  --color-tps17-muted: #8a4555;
  --color-tps17-muted: oklch(0.48 0.09 10);
  --color-tps17-line: #e7d8c8;
  --color-tps17-line: oklch(0.89 0.025 75);
  --font-tps17-serif: 'Playfair Display', Georgia, serif;
  --font-tps17-mono: 'JetBrains Mono', monospace;
  --font-tps17-sans: 'Public Sans', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
   w-full min-h-svh, so the section is sized correctly in the moment before the
   browser build has compiled its utilities. */

.tps-17 {
  width: 100%;
  min-height: 100svh;
}
3Fonts used by this demooptional — skip if you have your own type stack
<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=Playfair+Display:ital,wght@1,700&family=JetBrains+Mono:wght@700&family=Public+Sans:wght@400;500;600;800&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • The three-selector :has() rule plus its display: none base becomes nine independent hidden group-has-[#tps-17-eur:checked]:inline spans. Each price says which currency it belongs to; there is no shared rule to keep in step.
  • Five .ps-17__best … descendant overrides — heading, price border, suffix, list, bullets, CTA — disappear. The inverted card's children carry cream directly, and the CSS's color: #6b1224 !important specificity fight goes with them.
  • border-current replaces border: 1.5px solid currentColor plus two colour overrides: the outline CTA, the inverted CTA and the inverted-primary CTA all derive their border from their own text colour.
  • The tabs use named peers (peer/usd, peer/eur, peer/gbp) with explicit col-start-*, because three radios and three labels interleaved in one grid need both the naming and the placement stated.
  • md:scale-[1.04] absorbs both the featured lift and the 820px transform: none rule — the emphasis is a desktop enhancement by declaration.
  • --alpha(--color-tps17-wine/30) ties the featured card's drop shadow to its own background token rather than repeating an rgba() literal.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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 Currency Switcher Pricing Section
Source: https://codefronts.com/layouts/tailwind-css-pricing-sections/tailwind-currency-switcher-pricing-section/

Three currencies, nine prices, zero JavaScript: hidden radios plus group-has-[…]: swap every visible price across all three tiers at once. Burgundy on cream, with the featured tier inverted &mdash; and the port's clean-up is the inverted card, whose eight descendant override rules become ordinary utilities on the elements themselves.
## HTML
```html
<section class="tps-17 group w-full min-h-svh bg-tps17-paper font-tps17-sans text-tps17-ink px-[clamp(20px,4vw,40px)] py-[clamp(28px,5vw,56px)]" aria-label="Pricing">
  <header class="text-center mb-[clamp(24px,4vw,36px)]">
    <h2 class="m-0 mb-2 font-tps17-serif text-[clamp(26px,4vw,38px)] font-bold italic tracking-[-0.015em] text-tps17-wine">Pricing in your currency.</h2>
    <p class="m-0 mb-5 text-sm text-tps17-muted">Pay where you live, with the rate fixed at billing time.</p>
    <fieldset class="relative inline-grid grid-cols-3 m-0 p-0.75 rounded-full bg-white border-[1.5px] border-tps17-wine">
      <legend class="sr-only">Currency</legend>
      <input type="radio" name="tps-17__c" id="tps-17-usd" class="peer/usd sr-only" checked>
      <label for="tps-17-usd" class="col-start-1 row-start-1 px-[22px] py-1.75 rounded-full font-tps17-mono text-xs font-bold tracking-[0.1em] text-tps17-muted cursor-pointer transition-[background-color,color] duration-[180ms] peer-checked/usd:bg-tps17-wine peer-checked/usd:text-tps17-cream peer-focus-visible/usd:outline-2 peer-focus-visible/usd:outline-tps17-wine peer-focus-visible/usd:outline-offset-[3px] motion-reduce:transition-none">USD</label>
      <input type="radio" name="tps-17__c" id="tps-17-eur" class="peer/eur sr-only">
      <label for="tps-17-eur" class="col-start-2 row-start-1 px-[22px] py-1.75 rounded-full font-tps17-mono text-xs font-bold tracking-[0.1em] text-tps17-muted cursor-pointer transition-[background-color,color] duration-[180ms] peer-checked/eur:bg-tps17-wine peer-checked/eur:text-tps17-cream peer-focus-visible/eur:outline-2 peer-focus-visible/eur:outline-tps17-wine peer-focus-visible/eur:outline-offset-[3px] motion-reduce:transition-none">EUR</label>
      <input type="radio" name="tps-17__c" id="tps-17-gbp" class="peer/gbp sr-only">
      <label for="tps-17-gbp" class="col-start-3 row-start-1 px-[22px] py-1.75 rounded-full font-tps17-mono text-xs font-bold tracking-[0.1em] text-tps17-muted cursor-pointer transition-[background-color,color] duration-[180ms] peer-checked/gbp:bg-tps17-wine peer-checked/gbp:text-tps17-cream peer-focus-visible/gbp:outline-2 peer-focus-visible/gbp:outline-tps17-wine peer-focus-visible/gbp:outline-offset-[3px] motion-reduce:transition-none">GBP</label>
    </fieldset>
  </header>
  <div class="grid grid-cols-1 md:grid-cols-[repeat(3,minmax(0,1fr))] items-center gap-[18px] max-w-[380px] md:max-w-[1000px] mx-auto">
    <article class="relative flex flex-col px-[22px] pt-[26px] pb-[22px] rounded-[8px] bg-white border border-tps17-line">
      <h3 class="m-0 mb-3 font-tps17-serif text-[22px] font-bold italic text-tps17-wine">Petit</h3>
      <div class="flex items-baseline gap-1 mb-[18px] pb-[18px] border-b border-dashed border-tps17-line text-[38px] font-extrabold tracking-[-0.03em]">
        <span class="hidden group-has-[#tps-17-usd:checked]:inline">$12</span><span class="hidden group-has-[#tps-17-eur:checked]:inline">&euro;11</span><span class="hidden group-has-[#tps-17-gbp:checked]:inline">&pound;10</span>
        <em class="not-italic ml-1.5 text-[13px] font-medium text-tps17-muted">/month</em>
      </div>
      <ul class="list-none p-0 m-0 mb-5 flex-1 text-[13.5px]/[1.95] text-tps17-body">
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">Solo cellar</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">3 bottles tracked</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">Email reminders</li>
      </ul>
      <a href="#petit" class="block text-center px-4 py-2.75 rounded-[6px] bg-transparent text-tps17-wine border-[1.5px] border-current text-[13px] font-semibold uppercase tracking-[0.04em] no-underline transition-[background-color,color] duration-[180ms] hover:bg-tps17-wine hover:text-tps17-cream focus-visible:outline-2 focus-visible:outline-current focus-visible:outline-offset-[3px] motion-reduce:transition-none">Choose Petit</a>
    </article>
    <article class="relative flex flex-col px-[22px] pt-[26px] pb-[22px] rounded-[8px] bg-tps17-wine text-tps17-cream md:scale-[1.04] shadow-[0_18px_44px_--alpha(--color-tps17-wine/30)]">
      <span class="absolute -top-3 left-1/2 -translate-x-1/2 whitespace-nowrap px-2.75 py-1 rounded-full bg-tps17-paper text-tps17-wine border-[1.5px] border-tps17-wine font-tps17-mono text-[10.5px] font-bold uppercase tracking-[0.12em]">Sommelier&rsquo;s pick</span>
      <h3 class="m-0 mb-3 font-tps17-serif text-[22px] font-bold italic text-tps17-cream">Reserve</h3>
      <div class="flex items-baseline gap-1 mb-[18px] pb-[18px] border-b border-dashed border-tps17-cream/25 text-[38px] font-extrabold tracking-[-0.03em]">
        <span class="hidden group-has-[#tps-17-usd:checked]:inline">$36</span><span class="hidden group-has-[#tps-17-eur:checked]:inline">&euro;33</span><span class="hidden group-has-[#tps-17-gbp:checked]:inline">&pound;28</span>
        <em class="not-italic ml-1.5 text-[13px] font-medium text-tps17-cream/70">/month</em>
      </div>
      <ul class="list-none p-0 m-0 mb-5 flex-1 text-[13.5px]/[1.95] text-tps17-cream">
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-cream">Full cellar</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-cream">Unlimited bottles</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-cream">Tasting notes</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-cream">Pairing engine</li>
      </ul>
      <a href="#reserve" class="block text-center px-4 py-2.75 rounded-[6px] bg-tps17-cream text-tps17-wine border-[1.5px] border-tps17-cream text-[13px] font-semibold uppercase tracking-[0.04em] no-underline transition-[background-color,color] duration-[180ms] hover:bg-transparent hover:text-tps17-cream focus-visible:outline-2 focus-visible:outline-tps17-cream focus-visible:outline-offset-[3px] motion-reduce:transition-none">Choose Reserve</a>
    </article>
    <article class="relative flex flex-col px-[22px] pt-[26px] pb-[22px] rounded-[8px] bg-white border border-tps17-line">
      <h3 class="m-0 mb-3 font-tps17-serif text-[22px] font-bold italic text-tps17-wine">Grand Cru</h3>
      <div class="flex items-baseline gap-1 mb-[18px] pb-[18px] border-b border-dashed border-tps17-line text-[38px] font-extrabold tracking-[-0.03em]">
        <span class="hidden group-has-[#tps-17-usd:checked]:inline">$96</span><span class="hidden group-has-[#tps-17-eur:checked]:inline">&euro;88</span><span class="hidden group-has-[#tps-17-gbp:checked]:inline">&pound;76</span>
        <em class="not-italic ml-1.5 text-[13px] font-medium text-tps17-muted">/month</em>
      </div>
      <ul class="list-none p-0 m-0 mb-5 flex-1 text-[13.5px]/[1.95] text-tps17-body">
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">Multi-cellar</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">Insurance valuation</li>
        <li class="before:content-['\25C6'] before:inline-block before:w-5 before:text-[9px] before:text-tps17-wine">White-glove setup</li>
      </ul>
      <a href="#grand" class="block text-center px-4 py-2.75 rounded-[6px] bg-transparent text-tps17-wine border-[1.5px] border-current text-[13px] font-semibold uppercase tracking-[0.04em] no-underline transition-[background-color,color] duration-[180ms] hover:bg-tps17-wine hover:text-tps17-cream focus-visible:outline-2 focus-visible:outline-current focus-visible:outline-offset-[3px] motion-reduce:transition-none">Talk to us</a>
    </article>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tps17-paper: #faf6ee;
  --color-tps17-paper: oklch(0.97 0.012 85);
  --color-tps17-cream: #faf6ee;
  --color-tps17-cream: oklch(0.97 0.012 85);
  --color-tps17-ink: #2a0a14;
  --color-tps17-ink: oklch(0.21 0.06 10);
  --color-tps17-wine: #6b1224;
  --color-tps17-wine: oklch(0.34 0.13 15);
  --color-tps17-body: #4a2030;
  --color-tps17-body: oklch(0.32 0.07 8);
  --color-tps17-muted: #8a4555;
  --color-tps17-muted: oklch(0.48 0.09 10);
  --color-tps17-line: #e7d8c8;
  --color-tps17-line: oklch(0.89 0.025 75);
  --font-tps17-serif: 'Playfair Display', Georgia, serif;
  --font-tps17-mono: 'JetBrains Mono', monospace;
  --font-tps17-sans: 'Public Sans', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
   w-full min-h-svh, so the section is sized correctly in the moment before the
   browser build has compiled its utilities. */

.tps-17 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

One radio, nine prices. Every price cell carries all three currencies and lets a variant decide which one is inline:

<span class="hidden group-has-[#tps-17-usd:checked]:inline">$36</span>
<span class="hidden group-has-[#tps-17-eur:checked]:inline">€33</span>
<span class="hidden group-has-[#tps-17-gbp:checked]:inline">£28</span>

The CSS does the same thing with a three-selector rule and a .ps-17__price > span { display: none } base. Ported, there is no base rule and no selector list — each span says what it is for.

The inverted card stops being a special case. .ps-17__best h3, .ps-17__best ul, .ps-17__best .ps-17__price, .ps-17__best ul li::before, .ps-17__best .ps-17__cta — five descendant overrides, plus a !important on the primary CTA to win the specificity fight. In utilities the featured card's children simply carry cream instead of wine, and nothing overrides anything.

border-current is the right abstraction here. The CTA's border should always match its text. Stating that once means the outline card, the inverted card and the inverted-primary card all work without a border colour of their own.

Real currency amounts, not a conversion. The prices are three separate round numbers, as a wine merchant would actually list them — which is the honest reason this can be a CSS swap at all rather than arithmetic.

Make it yours

  • Currencies: three radios, and three spans per price. A fourth currency is four more spans — workable, but past that use JS.
  • Rates: the nine price spans are independent numbers. Round them per market rather than converting; that is the point of the pattern.
  • Palette: --color-tps17-wine, --color-tps17-cream, --color-tps17-line. The featured card is just those first two swapped.
  • Lift: md:scale-[1.04]. Drop it and the inverted palette alone still carries the emphasis.
  • Display face: --font-tps17-serif for the italic tier names, --font-tps17-mono for the tabs and the pill. The mono/serif tension is the whole boutique voice.

Gotchas — read before shipping

  • The checked radio and the un-hidden default must agree — here USD is checked and its spans are the ones a group-has variant reveals, so the card is correct before any interaction.
  • All nine prices are always in the DOM. A screen reader in USD mode will not read the hidden ones (display: none removes them from the accessibility tree), but keep them in the same order so the visual and reading order match.
  • sr-only on the radios, never hidden: the tabs must stay keyboard-operable as a radio group.
  • Inside the inverted card, do not use alpha text for the list. text-tps17-cream/85 on wine is borderline; the port keeps the list cream at full opacity and drops opacity only on the /month suffix, which is not body copy.
  • border-current means the CTA border inherits its card's text colour — if you give a CTA a different text-*, the border follows. That is the feature, but it surprises people.
  • Prices sit in a flex items-baseline row. Without items-baseline the /month suffix aligns to the 38px number's box and floats.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

:has() is Baseline since Firefox 121 (December 2023); group-has-* compiles straight to it. Without :has() support the tabs still focus and check, but the displayed currency never changes — so USD, the checked default, is what everyone sees.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Theme tokensset in @theme, used by a utility

Search CodeFronts

Loading…