15 CSS 3D Tilt Hover Cards03 / 15

Vanilla JSMIT licensed

Interactive Pricing Tier Card

A three-tier pricing row where every card tilts toward the cursor, but the featured Pro tier adds a scale boost and a violet glow that intensifies with hover — the classic SaaS landing-page trick for steering eyes (and clicks) to the plan you want sold. One shared handler drives all three cards.

Published

Live Demo
Try it

The code

<section class="thc-03" aria-label="Pricing tier tilt cards demo">
  <div class="thc-03__row">
    <article class="thc-03__card" tabindex="0" aria-label="Starter plan, 12 dollars per month">
      <h3 class="thc-03__tier">Starter</h3>
      <p class="thc-03__price"><strong>$12</strong><span>/mo</span></p>
      <ul class="thc-03__feats"><li>3 projects</li><li>Community support</li><li>1 GB storage</li></ul>
      <button class="thc-03__cta" type="button">Choose Starter</button>
    </article>
    <article class="thc-03__card thc-03__card--featured" tabindex="0" aria-label="Pro plan, 29 dollars per month, most popular">
      <span class="thc-03__badge">MOST POPULAR</span>
      <h3 class="thc-03__tier">Pro</h3>
      <p class="thc-03__price"><strong>$29</strong><span>/mo</span></p>
      <ul class="thc-03__feats"><li>Unlimited projects</li><li>Priority support</li><li>100 GB storage</li></ul>
      <button class="thc-03__cta thc-03__cta--solid" type="button">Choose Pro</button>
    </article>
    <article class="thc-03__card" tabindex="0" aria-label="Team plan, 79 dollars per month">
      <h3 class="thc-03__tier">Team</h3>
      <p class="thc-03__price"><strong>$79</strong><span>/mo</span></p>
      <ul class="thc-03__feats"><li>Everything in Pro</li><li>SSO &amp; audit log</li><li>Unlimited storage</li></ul>
      <button class="thc-03__cta" type="button">Choose Team</button>
    </article>
  </div>
</section>
.thc-03,
.thc-03 *,
.thc-03 *::before,
.thc-03 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.thc-03 {
  --glow: oklch(0.62 0.21 295);
  --ink: oklch(0.94 0.01 280);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: radial-gradient(90% 100% at 50% 0%,oklch(0.24 0.06 295 / .8),transparent 60%),oklch(0.15 0.02 285);
}

.thc-03 .thc-03__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
  width: min(760px,100%);
}

.thc-03 .thc-03__card {
  position: relative;
  flex: 1 1 200px;
  max-width: 240px;
  padding: 26px 22px;
  border-radius: 20px;
  background: linear-gradient(165deg,oklch(0.22 0.03 290),oklch(0.18 0.025 285));
  border: 1px solid rgba(255,255,255,.1);
  transform: perspective(1000px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) scale(var(--pop,1));
  transition: transform .55s cubic-bezier(.2,.9,.3,1.1),box-shadow .55s ease;
  box-shadow: 0 20px 44px -26px rgba(0,0,0,.8);
}

.thc-03 .thc-03__card.is-live {
  transition: transform .08s linear,box-shadow .3s ease;
}

.thc-03 .thc-03__card:focus-visible {
  outline: 3px solid var(--glow);
  outline-offset: 4px;
}

.thc-03 .thc-03__card--featured {
  border-color: color-mix(in oklab,var(--glow) 55%,transparent);
  box-shadow: 0 20px 50px -22px color-mix(in oklab,var(--glow) 55%,transparent);
}

.thc-03 .thc-03__card--featured.is-live {
  --pop: 1.045;
  box-shadow: 0 28px 64px -20px color-mix(in oklab,var(--glow) 75%,transparent);
}

.thc-03 .thc-03__badge {
  position: absolute;
  top: -11px;
  left: 50%;
  translate: -50% 0;
  font: 700 9.5px/1 'Segoe UI',sans-serif;
  letter-spacing: .12em;
  color: #fff;
  background: linear-gradient(120deg,var(--glow),oklch(0.55 0.2 330));
  padding: 7px 12px;
  border-radius: 999px;
}

.thc-03 .thc-03__tier {
  font-size: 15px;
  font-weight: 600;
  color: rgba(255,255,255,.75);
}

.thc-03 .thc-03__price {
  margin-top: 10px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.thc-03 .thc-03__price strong {
  font-size: 36px;
  font-weight: 800;
}

.thc-03 .thc-03__price span {
  font-size: 13px;
  color: rgba(255,255,255,.5);
}

.thc-03 .thc-03__feats {
  list-style: none;
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.thc-03 .thc-03__feats li {
  font-size: 13px;
  color: rgba(255,255,255,.7);
  padding-left: 18px;
  background: linear-gradient(var(--glow),var(--glow)) no-repeat 0 55%/8px 2px;
}

.thc-03 .thc-03__cta {
  margin-top: 20px;
  width: 100%;
  height: 40px;
  border-radius: 10px;
  font: 600 13px 'Segoe UI',sans-serif;
  cursor: pointer;
  color: var(--ink);
  background: transparent;
  border: 1px solid rgba(255,255,255,.25);
  transition: background .25s ease;
}

.thc-03 .thc-03__cta:hover {
  background: rgba(255,255,255,.08);
}

.thc-03 .thc-03__cta--solid {
  border: none;
  color: #fff;
  background: linear-gradient(120deg,var(--glow),oklch(0.55 0.2 330));
}

.thc-03 .thc-03__cta:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .thc-03 .thc-03__card {
    transition: none;
  }
}
(() => {
  const cards = document.querySelectorAll('.thc-03 .thc-03__card');
  if (!cards.length) return;
  if (!matchMedia('(hover:hover) and (pointer:fine)').matches) return;
  if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
  const MAX = 9;
  cards.forEach((card) => {          // one bind loop — adding a 4th tier is pure HTML
    card.addEventListener('pointermove', (e) => {
      const r = card.getBoundingClientRect();
      const px = (e.clientX - r.left) / r.width, py = (e.clientY - r.top) / r.height;
      card.style.setProperty('--rx', ((0.5 - py) * MAX).toFixed(2) + 'deg');
      card.style.setProperty('--ry', ((px - 0.5) * MAX).toFixed(2) + 'deg');
      card.classList.add('is-live'); // featured card also picks up its scale boost here
    });
    card.addEventListener('pointerleave', () => {
      card.classList.remove('is-live');
      card.style.setProperty('--rx', '0deg');
      card.style.setProperty('--ry', '0deg');
    });
  });
})();
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 3D Tilt Hover Card 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: Interactive Pricing Tier Card
Source: https://codefronts.com/components/css-3d-tilt-hover-cards/interactive-pricing-tier-card/

A three-tier pricing row where every card tilts toward the cursor, but the featured Pro tier adds a scale boost and a violet glow that intensifies with hover — the classic SaaS landing-page trick for steering eyes (and clicks) to the plan you want sold. One shared handler drives all three cards.
## HTML
```html
<section class="thc-03" aria-label="Pricing tier tilt cards demo">
  <div class="thc-03__row">
    <article class="thc-03__card" tabindex="0" aria-label="Starter plan, 12 dollars per month">
      <h3 class="thc-03__tier">Starter</h3>
      <p class="thc-03__price"><strong>$12</strong><span>/mo</span></p>
      <ul class="thc-03__feats"><li>3 projects</li><li>Community support</li><li>1 GB storage</li></ul>
      <button class="thc-03__cta" type="button">Choose Starter</button>
    </article>
    <article class="thc-03__card thc-03__card--featured" tabindex="0" aria-label="Pro plan, 29 dollars per month, most popular">
      <span class="thc-03__badge">MOST POPULAR</span>
      <h3 class="thc-03__tier">Pro</h3>
      <p class="thc-03__price"><strong>$29</strong><span>/mo</span></p>
      <ul class="thc-03__feats"><li>Unlimited projects</li><li>Priority support</li><li>100 GB storage</li></ul>
      <button class="thc-03__cta thc-03__cta--solid" type="button">Choose Pro</button>
    </article>
    <article class="thc-03__card" tabindex="0" aria-label="Team plan, 79 dollars per month">
      <h3 class="thc-03__tier">Team</h3>
      <p class="thc-03__price"><strong>$79</strong><span>/mo</span></p>
      <ul class="thc-03__feats"><li>Everything in Pro</li><li>SSO &amp; audit log</li><li>Unlimited storage</li></ul>
      <button class="thc-03__cta" type="button">Choose Team</button>
    </article>
  </div>
</section>
```
## CSS
```css
.thc-03,
.thc-03 *,
.thc-03 *::before,
.thc-03 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.thc-03 {
  --glow: oklch(0.62 0.21 295);
  --ink: oklch(0.94 0.01 280);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: radial-gradient(90% 100% at 50% 0%,oklch(0.24 0.06 295 / .8),transparent 60%),oklch(0.15 0.02 285);
}

.thc-03 .thc-03__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
  width: min(760px,100%);
}

.thc-03 .thc-03__card {
  position: relative;
  flex: 1 1 200px;
  max-width: 240px;
  padding: 26px 22px;
  border-radius: 20px;
  background: linear-gradient(165deg,oklch(0.22 0.03 290),oklch(0.18 0.025 285));
  border: 1px solid rgba(255,255,255,.1);
  transform: perspective(1000px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) scale(var(--pop,1));
  transition: transform .55s cubic-bezier(.2,.9,.3,1.1),box-shadow .55s ease;
  box-shadow: 0 20px 44px -26px rgba(0,0,0,.8);
}

.thc-03 .thc-03__card.is-live {
  transition: transform .08s linear,box-shadow .3s ease;
}

.thc-03 .thc-03__card:focus-visible {
  outline: 3px solid var(--glow);
  outline-offset: 4px;
}

.thc-03 .thc-03__card--featured {
  border-color: color-mix(in oklab,var(--glow) 55%,transparent);
  box-shadow: 0 20px 50px -22px color-mix(in oklab,var(--glow) 55%,transparent);
}

.thc-03 .thc-03__card--featured.is-live {
  --pop: 1.045;
  box-shadow: 0 28px 64px -20px color-mix(in oklab,var(--glow) 75%,transparent);
}

.thc-03 .thc-03__badge {
  position: absolute;
  top: -11px;
  left: 50%;
  translate: -50% 0;
  font: 700 9.5px/1 'Segoe UI',sans-serif;
  letter-spacing: .12em;
  color: #fff;
  background: linear-gradient(120deg,var(--glow),oklch(0.55 0.2 330));
  padding: 7px 12px;
  border-radius: 999px;
}

.thc-03 .thc-03__tier {
  font-size: 15px;
  font-weight: 600;
  color: rgba(255,255,255,.75);
}

.thc-03 .thc-03__price {
  margin-top: 10px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.thc-03 .thc-03__price strong {
  font-size: 36px;
  font-weight: 800;
}

.thc-03 .thc-03__price span {
  font-size: 13px;
  color: rgba(255,255,255,.5);
}

.thc-03 .thc-03__feats {
  list-style: none;
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.thc-03 .thc-03__feats li {
  font-size: 13px;
  color: rgba(255,255,255,.7);
  padding-left: 18px;
  background: linear-gradient(var(--glow),var(--glow)) no-repeat 0 55%/8px 2px;
}

.thc-03 .thc-03__cta {
  margin-top: 20px;
  width: 100%;
  height: 40px;
  border-radius: 10px;
  font: 600 13px 'Segoe UI',sans-serif;
  cursor: pointer;
  color: var(--ink);
  background: transparent;
  border: 1px solid rgba(255,255,255,.25);
  transition: background .25s ease;
}

.thc-03 .thc-03__cta:hover {
  background: rgba(255,255,255,.08);
}

.thc-03 .thc-03__cta--solid {
  border: none;
  color: #fff;
  background: linear-gradient(120deg,var(--glow),oklch(0.55 0.2 330));
}

.thc-03 .thc-03__cta:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .thc-03 .thc-03__card {
    transition: none;
  }
}
```

## JavaScript
```js
(() => {
  const cards = document.querySelectorAll('.thc-03 .thc-03__card');
  if (!cards.length) return;
  if (!matchMedia('(hover:hover) and (pointer:fine)').matches) return;
  if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
  const MAX = 9;
  cards.forEach((card) => {          // one bind loop — adding a 4th tier is pure HTML
    card.addEventListener('pointermove', (e) => {
      const r = card.getBoundingClientRect();
      const px = (e.clientX - r.left) / r.width, py = (e.clientY - r.top) / r.height;
      card.style.setProperty('--rx', ((0.5 - py) * MAX).toFixed(2) + 'deg');
      card.style.setProperty('--ry', ((px - 0.5) * MAX).toFixed(2) + 'deg');
      card.classList.add('is-live'); // featured card also picks up its scale boost here
    });
    card.addEventListener('pointerleave', () => {
      card.classList.remove('is-live');
      card.style.setProperty('--rx', '0deg');
      card.style.setProperty('--ry', '0deg');
    });
  });
})();
```

How this works

One pointermove listener per card writes the standard --rx/--ry pair; all three share the same bind loop, so adding a fourth tier is pure HTML. The featured card layers scale(var(--pop,1)) into the same transform and flips --pop to 1.045 via the is-live class — combining scale and rotation in ONE transform declaration is what keeps the motion composited.

The glow is a box-shadow whose color comes from color-mix(in oklab, violet 55%, transparent), widened on hover. Price digits use font-variant-numeric:tabular-nums so monthly/annual toggles (when you wire one) never reflow the card width.

Make it yours

  • Feature a different tier by moving the --featured class — glow, scale and badge all follow.
  • Tighten MAX to 6–8° if your pricing row sits above the fold where big motion distracts.
  • Re-brand the glow hue in one place: the --glow custom property.
  • Add a monthly/annual switch — tabular-nums already guarantees stable digit widths.

Gotchas — read before shipping

  • Tilt + scale must live in the SAME transform value; a separate scale property on hover forces a second compositing layer and can flicker in Safari.
  • Keep the featured card's scale under ~1.05 or it will overlap neighbours at narrow widths.
  • The CTA needs a real :focus-visible ring — pricing cards are the most keyboard-visited cards on a landing page.

Browser support

ChromeSafariFirefoxEdge
111+16.4+113+111+

color-mix + oklch for the glow; the tilt/scale composite works everywhere with hex fallbacks.

Techniques used in this demo

Search CodeFronts

Loading…