EmpireUI
Get Pro
← Blog8 min read#glassmorphism#pricing#card

Glassmorphism Pricing Card: Frosted-Glass Tier Design in React

Build a frosted-glass pricing card in React and Tailwind with backdrop-filter, animated highlights, and accessible contrast — no design system required.

frosted glass pricing card UI design on gradient background

Why Glassmorphism Works for Pricing Cards

Pricing sections are one of the highest-stakes pieces of UI on any SaaS page. You're asking someone to make a financial decision based on what they see in roughly three seconds. Most teams default to a white card with a drop-shadow, which works, but it's also what every competitor is shipping. A frosted-glass card signals that you actually care about craft.

The glassmorphism approach — translucent background, backdrop-filter: blur(), faint border highlight — creates visual hierarchy almost for free. Your 'Pro' tier floats. Literally. The blur effect makes the card feel elevated above the gradient background without you needing to reach for box-shadow: 0 40px 100px rgba(0,0,0,0.4) and hope for the best.

Honestly, the technique has matured a lot since the early 2021 hype cycle. Back then every implementation looked like frosted toilet glass. In 2026, the constraint is knowing *when to stop* — one layer of glass over a well-chosen gradient, not five nested translucent divs stacked like a terrible lasagne. If you want to see where the line is, the glassmorphism components section is worth twenty minutes of browsing before you write a line of CSS.

One more thing — pricing cards specifically benefit from the see-through quality because the background gradient bleeds through differently at each tier. Your Starter card might show cooler blues, your Pro card warmer purples, while they all share the same markup. That differentiation comes almost entirely from the background you choose, not from the component itself.

Setting Up the Background: Gradients That Actually Blur Well

The background is half the component. backdrop-filter needs *something interesting* behind the card or you just get a blurry grey rectangle. Here's what you need: a gradient with at least two strongly-contrasting hues, enough saturation that the blur produces visible colour mixing, and a background-attachment: fixed or a full-viewport container so the gradient doesn't scroll away from the cards.

/* globals.css or your layout wrapper */
.pricing-bg {
  min-height: 100vh;
  background: linear-gradient(
    135deg,
    #6366f1 0%,   /* indigo */
    #8b5cf6 35%,  /* violet */
    #ec4899 70%,  /* pink */
    #f97316 100%  /* orange */
  );
}

With Tailwind you'd write bg-gradient-to-br from-indigo-500 via-purple-600 to-pink-500 on the section wrapper. That's fine for a quick pass but you'll probably want to extend the gradient config for exact stops. Worth noting: if you're using the gradient generator, you can export the CSS directly and paste it into your Tailwind config under backgroundImage.

Don't use an image as your background and expect blur to save you. Photographic backgrounds with high detail create visual noise once blur(16px) processes them through a translucent surface. Flat gradients, mesh gradients (two or three radial blobs), or solid vibrant colours all work better. Keep the blur radius between 12px and 20px — below 12px it looks like a rendering bug, above 24px you're obscuring too much context.

The React Component: Building Each Pricing Tier

Here's the core card component. It takes tier data as props, applies the glass treatment via Tailwind utility classes, and handles a 'featured' flag for the Pro tier that adds a coloured ring and a slightly different opacity.

// components/GlassPricingCard.tsx
import { Check } from 'lucide-react'

type Tier = {
  name: string
  price: string
  period: string
  features: string[]
  cta: string
  featured?: boolean
}

export function GlassPricingCard({ tier }: { tier: Tier }) {
  const base =
    'relative rounded-2xl border p-8 flex flex-col gap-6 ' +
    'backdrop-blur-xl bg-white/10 shadow-lg transition-transform duration-200 ' +
    'hover:-translate-y-1'

  const ring = tier.featured
    ? 'border-white/40 ring-2 ring-violet-400/60'
    : 'border-white/20'

  return (
    <div className={`${base} ${ring}`}>
      {tier.featured && (
        <span className="absolute -top-3 left-1/2 -translate-x-1/2 rounded-full bg-violet-500 px-4 py-1 text-xs font-semibold text-white">
          Most Popular
        </span>
      )}

      <div>
        <p className="text-sm font-medium text-white/70">{tier.name}</p>
        <p className="mt-1 text-4xl font-bold text-white">
          {tier.price}
          <span className="text-lg font-normal text-white/60">/{tier.period}</span>
        </p>
      </div>

      <ul className="flex flex-col gap-3 flex-1">
        {tier.features.map((f) => (
          <li key={f} className="flex items-center gap-2 text-sm text-white/80">
            <Check size={14} className="text-violet-300 shrink-0" />
            {f}
          </li>
        ))}
      </ul>

      <button
        className={
          tier.featured
            ? 'rounded-xl bg-violet-500 px-4 py-3 text-sm font-semibold text-white hover:bg-violet-400 transition-colors'
            : 'rounded-xl border border-white/30 px-4 py-3 text-sm font-semibold text-white hover:bg-white/10 transition-colors'
        }
      >
        {tier.cta}
      </button>
    </div>
  )
}

The class backdrop-blur-xl maps to backdrop-filter: blur(24px) in Tailwind v3+. If you want finer control — say, exactly 16px — drop a custom class: [backdrop-filter:blur(16px)]. Either way, you need bg-white/10 (or some non-zero alpha background) on the same element, because backdrop-filter has no visible effect on a fully transparent element.

That said, the hover translate is important. Without any interaction feedback, glass cards feel inert. A 1px or 2px upward translate on hover is subtle enough not to break the composition but gives users confirmation that the card is interactive. Keep the duration-200 — any slower and it starts feeling sluggish on desktop.

One more thing — make sure your card wrapper has overflow: hidden or rounded-2xl applied to a parent that clips the glass edge. Without clipping, the blur bleeds outside the border-radius on Safari (tested on Safari 17.4) and you get ugly soft halos at the corners.

Composing the Pricing Section

Wiring the cards together is the easy part. You need a data array, the gradient wrapper, and a responsive grid. Here's the full section:

// app/pricing/page.tsx (or wherever your pricing lives)
import { GlassPricingCard } from '@/components/GlassPricingCard'

const tiers = [
  {
    name: 'Starter',
    price: '$0',
    period: 'month',
    features: ['5 projects', '2GB storage', 'Community support', 'Basic analytics'],
    cta: 'Get started free',
  },
  {
    name: 'Pro',
    price: '$29',
    period: 'month',
    features: [
      'Unlimited projects',
      '50GB storage',
      'Priority support',
      'Advanced analytics',
      'Custom domain',
    ],
    cta: 'Start Pro trial',
    featured: true,
  },
  {
    name: 'Team',
    price: '$79',
    period: 'month',
    features: [
      'Everything in Pro',
      '200GB storage',
      'Dedicated Slack channel',
      'SSO / SAML',
      'SLA guarantee',
    ],
    cta: 'Talk to sales',
  },
]

export default function PricingPage() {
  return (
    <section className="min-h-screen bg-gradient-to-br from-indigo-600 via-purple-600 to-pink-500 px-4 py-24">
      <div className="mx-auto max-w-5xl">
        <h2 className="mb-4 text-center text-4xl font-bold text-white">Simple pricing</h2>
        <p className="mb-16 text-center text-white/70">No hidden fees. Cancel any time.</p>

        <div className="grid grid-cols-1 gap-6 md:grid-cols-3">
          {tiers.map((tier) => (
            <GlassPricingCard key={tier.name} tier={tier} />
          ))}
        </div>
      </div>
    </section>
  )
}

In practice, the md:grid-cols-3 layout works fine at 768px and above. Below that, stack them vertically and put the featured card first in your data array — it renders at the top on mobile where scroll intention is clearest. Don't centre-highlight the middle card on mobile; users don't see three columns side-by-side, so the 'middle is best' visual cue evaporates.

Quick aside: if you're using Next.js App Router with page.tsx, make sure you're not running this inside a Server Component that tries to import client-only state. If you're adding a billing toggle (monthly vs annual), wrap just the toggle + price display in a 'use client' component and keep the surrounding layout as a Server Component. No need to client-ify the whole page.

Accessibility and Contrast — Don't Skip This

This is where most glass card tutorials fall apart. White text on a translucent white surface over a gradient is a contrast disaster. WCAG 2.1 AA requires a 4.5:1 contrast ratio for normal text. With bg-white/10 and a saturated gradient behind it, your text-white/70 might not make that cut depending on the gradient stop underneath.

The fix is layered. First, push the card background opacity up slightly: bg-white/15 gives just enough extra diffusion. Second, make your body text fully opaque white (text-white) and use text-white/70 only for secondary labels like tier names and period suffixes. Third, run your final colour combination through the WebAIM contrast checker — use the hex values Chrome DevTools reports for the computed blended colour, not just white on indigo.

/* Fallback for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  .glass-card {
    transition: none;
  }
}

/* Fallback for browsers that don't support backdrop-filter */
@supports not (backdrop-filter: blur(1px)) {
  .glass-card {
    background-color: rgba(99, 102, 241, 0.85); /* solid indigo */
  }
}

Look, accessibility on glass UIs isn't optional if you're shipping to real users. The good news is the fallback actually looks decent — a semi-opaque solid card is still miles better than a plain white box. You won't lose the premium feel; you'll just lose the blur for a small minority of users, which is an acceptable trade.

Also worth considering: the CTA buttons need enough contrast independently of the card background. The featured bg-violet-500 button against a bg-white/10 card surface is fine. The ghost button with border-white/30 is borderline — bump it to border-white/50 and text-white (not text-white/80) and you're safe.

Adding Polish: Animated Highlight and Inner Glow

The base component looks clean but you can push it further with two micro-details that take about 15 minutes to add: an animated shimmer on the top edge of the featured card, and a soft inner glow using box-shadow with a positive spread.

// Add this to GlassPricingCard for the featured tier
{tier.featured && (
  <div
    aria-hidden="true"
    className="pointer-events-none absolute inset-x-0 top-0 h-px"
    style={{
      background:
        'linear-gradient(90deg, transparent, rgba(167,139,250,0.8), transparent)',
      animation: 'shimmer 3s ease-in-out infinite',
    }}
  />
)}
/* In your global CSS */
@keyframes shimmer {
  0%, 100% { opacity: 0.4; transform: scaleX(0.8); }
  50%       { opacity: 1;   transform: scaleX(1); }
}

That 1px line at the top of the card catches light like the edge of real glass. It's subtle — most users won't consciously notice it — but the card feels more premium. If you want to go deeper on these kinds of kinetic surface effects, there's a great deal more to explore in the glassmorphism generator where you can tweak blur, opacity, and border highlight values live and copy the CSS output.

For the inner glow, add shadow-[inset_0_1px_0_rgba(255,255,255,0.2)] to the card div. That Tailwind arbitrary value creates a 1px inset shadow on the top edge, simulating light hitting the top surface of glass. Stack it with shadow-lg for the drop shadow: className="... shadow-lg shadow-[inset_0_1px_0_rgba(255,255,255,0.2)]". You can't stack shadow utilities in Tailwind by default — use [box-shadow:0_20px_40px_rgba(0,0,0,0.3),inset_0_1px_0_rgba(255,255,255,0.2)] as a single arbitrary value if you need both in one property.

Performance, Mobile, and Final Checklist

backdrop-filter is GPU-accelerated but it's not free. On mid-range Android phones from 2022, six overlapping blur layers can drop you below 60fps during scroll. For a pricing page — typically static, below the fold — this usually isn't a problem. But if you're embedding pricing cards inside a scroll-heavy page with other animated elements, add will-change: transform to the cards so the browser can layer-promote them ahead of time.

Quick checklist before you ship: confirm backdrop-filter is applied and not stripped by PostCSS or a CSS minimiser; verify the gradient background spans the full viewport height (not just the card height, or you get a clipped blur at the bottom); check that overflow: hidden is on the card so border-radius clips the shimmer element; and run Lighthouse on mobile — if your LCP is the pricing section hero text, the gradient background won't block it but an unoptimised image background would.

That said, if you want a head start rather than building all this from scratch, the Empire UI component library has production-ready glass card variants already. You can grab the component, drop in your tier data, and spend your time on the billing logic instead of debugging Firefox blur rendering.

In practice, most SaaS teams that switch from a standard white card to a glass card on their pricing page report better time-on-section metrics — users interact with the tier toggle more, they hover over each card. That's anecdotal, but the visual weight difference is real. A frosted card in front of a gradient pulls focus more deliberately than a white card on a white page. Whether that translates to conversion depends on the rest of your page, but the glass card at least gives you something to A/B test that isn't just copy.

FAQ

Does backdrop-filter work in all major browsers in 2026?

Yes — Chromium, Firefox, and Safari all support it fully. The only edge case is Firefox on Linux with hardware acceleration disabled, so add a @supports not (backdrop-filter) solid-colour fallback to cover that.

How do I stop the glass card from looking washed out on mobile?

Increase the background opacity slightly — try bg-white/15 instead of bg-white/10 — and make sure your underlying gradient saturated enough. Pale gradients produce dull blur; you need strong hue contrast between gradient stops.

Can I use this with Next.js App Router?

Absolutely. Keep the card as a Server Component unless you're adding client state like a billing toggle. Wrap only the interactive parts in 'use client' — no need to client-ify the whole pricing page.

What's the right blur value for a pricing card?

Between 12px and 20px for most screens. backdrop-blur-xl in Tailwind gives you 24px which is on the heavier side — backdrop-blur-lg (16px) is often a better balance between the frosted effect and legibility.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Glassmorphism Dashboard: Full Admin UI with Frosted-Glass CardsGlassmorphism Card Design: 7 Patterns That Actually WorkTailwind Pricing Section: 3-Tier Layout with Annual TogglePricing Table React Component: 3-Tier, Annual Toggle, Highlight