09 / 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 Neon Glowing Conic-Border Product Card
A dark card wrapped in a conic-gradient border that ignites and spins on hover, with a blurred copy of itself throwing the glow. The rotation needs a registered @property --angle and a @keyframes — both stylesheet rules — but the animation itself becomes a real utility, because --animate-tpc09-spin in @theme mints animate-tpc09-spin.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tpc-09 w-full min-h-svh grid place-items-center font-tpc09-sans px-5 py-14 bg-[radial-gradient(120%_120%_at_50%_20%,oklch(0.16_0.02_300),oklch(0.09_0.01_300))]" aria-label="Neon glowing border product card demo">
<article class="group relative isolate w-80 max-w-full p-0.5 rounded-[20px]">
<span aria-hidden="true" class="absolute inset-0 -z-10 rounded-[20px] opacity-50 bg-[conic-gradient(from_var(--angle),var(--color-tpc09-c1),var(--color-tpc09-c2),var(--color-tpc09-c3),var(--color-tpc09-c1))] animate-tpc09-spin [animation-play-state:paused] transition-opacity duration-400 group-hover:opacity-100 group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<span aria-hidden="true" class="absolute inset-0 -z-10 rounded-[20px] opacity-25 blur-[18px] bg-[conic-gradient(from_var(--angle),var(--color-tpc09-c1),var(--color-tpc09-c2),var(--color-tpc09-c3),var(--color-tpc09-c1))] animate-tpc09-spin [animation-play-state:paused] transition-opacity duration-400 group-hover:opacity-70 group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<div class="rounded-[18px] overflow-hidden bg-tpc09-face">
<div role="img" aria-label="Product photo: Flux automatic watch" class="aspect-[16/11] bg-linear-[135deg,var(--color-tpc09-ph),var(--color-tpc09-ph2)] bg-[url('https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&h=550&fit=crop&q=70')] bg-cover bg-center"></div>
<div class="px-5.5 pt-5 pb-5.5">
<p class="font-tpc09-mono text-[11px] tracking-[0.2em] uppercase text-tpc09-c2">Flux</p>
<h3 class="mt-1.5 text-[18px] font-semibold text-tpc09-ink text-pretty">Flux Automatic Watch</h3>
<div class="mt-4.5 flex items-center justify-between gap-3">
<p class="text-[22px] font-extrabold text-tpc09-hi tabular-nums">$460</p>
<button type="button" class="min-h-11 h-11 px-5.5 rounded-xl border-0 bg-linear-[120deg,var(--color-tpc09-c1),var(--color-tpc09-c2)] text-tpc09-onacc text-sm font-bold cursor-pointer transition-[filter,scale] duration-200 hover:brightness-110 active:scale-97 focus-visible:outline-3 focus-visible:outline-tpc09-c1 focus-visible:outline-offset-2 motion-reduce:transition-none">Add to Cart</button>
</div>
</div>
</div>
</article>
</section><section class="tpc-09 w-full min-h-svh grid place-items-center font-tpc09-sans px-5 py-14 bg-[radial-gradient(120%_120%_at_50%_20%,oklch(0.16_0.02_300),oklch(0.09_0.01_300))]" aria-label="Neon glowing border product card demo">
<article class="group relative isolate w-80 max-w-full p-0.5 rounded-[20px]">
<span aria-hidden="true" class="absolute inset-0 -z-10 rounded-[20px] opacity-50 bg-[conic-gradient(from_var(--angle),var(--color-tpc09-c1),var(--color-tpc09-c2),var(--color-tpc09-c3),var(--color-tpc09-c1))] animate-tpc09-spin [animation-play-state:paused] transition-opacity duration-400 group-hover:opacity-100 group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<span aria-hidden="true" class="absolute inset-0 -z-10 rounded-[20px] opacity-25 blur-[18px] bg-[conic-gradient(from_var(--angle),var(--color-tpc09-c1),var(--color-tpc09-c2),var(--color-tpc09-c3),var(--color-tpc09-c1))] animate-tpc09-spin [animation-play-state:paused] transition-opacity duration-400 group-hover:opacity-70 group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<div class="rounded-[18px] overflow-hidden bg-tpc09-face">
<div role="img" aria-label="Product photo: Flux automatic watch" class="aspect-[16/11] bg-linear-[135deg,var(--color-tpc09-ph),var(--color-tpc09-ph2)] bg-[url('https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&h=550&fit=crop&q=70')] bg-cover bg-center"></div>
<div class="px-5.5 pt-5 pb-5.5">
<p class="font-tpc09-mono text-[11px] tracking-[0.2em] uppercase text-tpc09-c2">Flux</p>
<h3 class="mt-1.5 text-[18px] font-semibold text-tpc09-ink text-pretty">Flux Automatic Watch</h3>
<div class="mt-4.5 flex items-center justify-between gap-3">
<p class="text-[22px] font-extrabold text-tpc09-hi tabular-nums">$460</p>
<button type="button" class="min-h-11 h-11 px-5.5 rounded-xl border-0 bg-linear-[120deg,var(--color-tpc09-c1),var(--color-tpc09-c2)] text-tpc09-onacc text-sm font-bold cursor-pointer transition-[filter,scale] duration-200 hover:brightness-110 active:scale-97 focus-visible:outline-3 focus-visible:outline-tpc09-c1 focus-visible:outline-offset-2 motion-reduce:transition-none">Add to Cart</button>
</div>
</div>
</div>
</article>
</section>@import "tailwindcss";
@theme {
--color-tpc09-face: #0b0a11;
--color-tpc09-face: oklch(0.13 0.012 300);
--color-tpc09-ph: #171522;
--color-tpc09-ph: oklch(0.19 0.018 300);
--color-tpc09-ph2: #0f0e18;
--color-tpc09-ph2: oklch(0.15 0.015 300);
--color-tpc09-ink: #f1eefb;
--color-tpc09-ink: oklch(0.95 0.01 300);
--color-tpc09-hi: #ffffff;
--color-tpc09-hi: oklch(1 0 0);
--color-tpc09-onacc: #0a0713;
--color-tpc09-onacc: oklch(0.13 0.02 300);
--color-tpc09-c1: #6fd8ee;
--color-tpc09-c1: oklch(0.82 0.16 200);
--color-tpc09-c2: #e055b0;
--color-tpc09-c2: oklch(0.72 0.24 330);
--color-tpc09-c3: #f2c94c;
--color-tpc09-c3: oklch(0.85 0.18 90);
--font-tpc09-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc09-mono: 'JetBrains Mono', ui-monospace, monospace;
/* An @theme --animate-* entry mints a real utility: animate-tpc09-spin. */
--animate-tpc09-spin: tpc-09-spin 6s linear infinite;
}
/* The registered angle: unregistered, --angle is a string and will not
interpolate, so the border would jump rather than sweep. */
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes tpc-09-spin {
to {
--angle: 360deg;
}
}
.tpc-09 {
width: 100%;
min-height: 100svh;
}
.tpc-09 ::selection {
background: var(--color-tpc09-c2);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tpc09-face: #0b0a11;
--color-tpc09-face: oklch(0.13 0.012 300);
--color-tpc09-ph: #171522;
--color-tpc09-ph: oklch(0.19 0.018 300);
--color-tpc09-ph2: #0f0e18;
--color-tpc09-ph2: oklch(0.15 0.015 300);
--color-tpc09-ink: #f1eefb;
--color-tpc09-ink: oklch(0.95 0.01 300);
--color-tpc09-hi: #ffffff;
--color-tpc09-hi: oklch(1 0 0);
--color-tpc09-onacc: #0a0713;
--color-tpc09-onacc: oklch(0.13 0.02 300);
--color-tpc09-c1: #6fd8ee;
--color-tpc09-c1: oklch(0.82 0.16 200);
--color-tpc09-c2: #e055b0;
--color-tpc09-c2: oklch(0.72 0.24 330);
--color-tpc09-c3: #f2c94c;
--color-tpc09-c3: oklch(0.85 0.18 90);
--font-tpc09-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc09-mono: 'JetBrains Mono', ui-monospace, monospace;
/* An @theme --animate-* entry mints a real utility: animate-tpc09-spin. */
--animate-tpc09-spin: tpc-09-spin 6s linear infinite;
}
/* The registered angle: unregistered, --angle is a string and will not
interpolate, so the border would jump rather than sweep. */
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes tpc-09-spin {
to {
--angle: 360deg;
}
}
.tpc-09 {
width: 100%;
min-height: 100svh;
}
.tpc-09 ::selection {
background: var(--color-tpc09-c2);
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-tpc09-face: #0b0a11;
--color-tpc09-face: oklch(0.13 0.012 300);
--color-tpc09-ph: #171522;
--color-tpc09-ph: oklch(0.19 0.018 300);
--color-tpc09-ph2: #0f0e18;
--color-tpc09-ph2: oklch(0.15 0.015 300);
--color-tpc09-ink: #f1eefb;
--color-tpc09-ink: oklch(0.95 0.01 300);
--color-tpc09-hi: #ffffff;
--color-tpc09-hi: oklch(1 0 0);
--color-tpc09-onacc: #0a0713;
--color-tpc09-onacc: oklch(0.13 0.02 300);
--color-tpc09-c1: #6fd8ee;
--color-tpc09-c1: oklch(0.82 0.16 200);
--color-tpc09-c2: #e055b0;
--color-tpc09-c2: oklch(0.72 0.24 330);
--color-tpc09-c3: #f2c94c;
--color-tpc09-c3: oklch(0.85 0.18 90);
--font-tpc09-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc09-mono: 'JetBrains Mono', ui-monospace, monospace;
/* An @theme --animate-* entry mints a real utility: animate-tpc09-spin. */
--animate-tpc09-spin: tpc-09-spin 6s linear infinite;
}
/* The registered angle: unregistered, --angle is a string and will not
interpolate, so the border would jump rather than sweep. */
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes tpc-09-spin {
to {
--angle: 360deg;
}
}
.tpc-09 {
width: 100%;
min-height: 100svh;
}
.tpc-09 ::selection {
background: var(--color-tpc09-c2);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tpc09-face: #0b0a11;
--color-tpc09-face: oklch(0.13 0.012 300);
--color-tpc09-ph: #171522;
--color-tpc09-ph: oklch(0.19 0.018 300);
--color-tpc09-ph2: #0f0e18;
--color-tpc09-ph2: oklch(0.15 0.015 300);
--color-tpc09-ink: #f1eefb;
--color-tpc09-ink: oklch(0.95 0.01 300);
--color-tpc09-hi: #ffffff;
--color-tpc09-hi: oklch(1 0 0);
--color-tpc09-onacc: #0a0713;
--color-tpc09-onacc: oklch(0.13 0.02 300);
--color-tpc09-c1: #6fd8ee;
--color-tpc09-c1: oklch(0.82 0.16 200);
--color-tpc09-c2: #e055b0;
--color-tpc09-c2: oklch(0.72 0.24 330);
--color-tpc09-c3: #f2c94c;
--color-tpc09-c3: oklch(0.85 0.18 90);
--font-tpc09-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc09-mono: 'JetBrains Mono', ui-monospace, monospace;
/* An @theme --animate-* entry mints a real utility: animate-tpc09-spin. */
--animate-tpc09-spin: tpc-09-spin 6s linear infinite;
}
/* The registered angle: unregistered, --angle is a string and will not
interpolate, so the border would jump rather than sweep. */
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes tpc-09-spin {
to {
--angle: 360deg;
}
}
.tpc-09 {
width: 100%;
min-height: 100svh;
}
.tpc-09 ::selection {
background: var(--color-tpc09-c2);
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&family=JetBrains+Mono:wght@400;500;600&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&family=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
--animate-tpc09-spinin@themeis the headline: the keyframes stay a stylesheet rule, but applying them becomesanimate-tpc09-spin, which then composes withmotion-reduce:animate-nonelike any other utility.- Both gradient layers became real
aria-hiddenspans. As pseudo-elements they would needbefore:/after:variants on a 120-character gradient pluscontent-['']— unreadable, and impossible to give different blur values cleanly. [animation-play-state:paused]is arbitrary and always will be; it is a state, not a design value. Pairing it with agroup-hover:variant is exactly what the CSS version did with a descendant selector.isolate+-z-10replaces the original'sisolation:isolate+z-index:-1one-for-one, and both are core utilities.- Enhancement over the CSS original: the two glow layers now fade independently (border 50→100%, glow 25→70%), which makes ignition read as a two-stage event rather than a single opacity jump.
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 Neon Glowing Conic-Border Product Card
Source: https://codefronts.com/components/tailwind-css-product-cards/tailwind-neon-glowing-conic-border-product-card/
A dark card wrapped in a conic-gradient border that ignites and spins on hover, with a blurred copy of itself throwing the glow. The rotation needs a registered @property --angle and a @keyframes — both stylesheet rules — but the animation itself becomes a real utility, because --animate-tpc09-spin in @theme mints animate-tpc09-spin.
## HTML
```html
<section class="tpc-09 w-full min-h-svh grid place-items-center font-tpc09-sans px-5 py-14 bg-[radial-gradient(120%_120%_at_50%_20%,oklch(0.16_0.02_300),oklch(0.09_0.01_300))]" aria-label="Neon glowing border product card demo">
<article class="group relative isolate w-80 max-w-full p-0.5 rounded-[20px]">
<span aria-hidden="true" class="absolute inset-0 -z-10 rounded-[20px] opacity-50 bg-[conic-gradient(from_var(--angle),var(--color-tpc09-c1),var(--color-tpc09-c2),var(--color-tpc09-c3),var(--color-tpc09-c1))] animate-tpc09-spin [animation-play-state:paused] transition-opacity duration-400 group-hover:opacity-100 group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<span aria-hidden="true" class="absolute inset-0 -z-10 rounded-[20px] opacity-25 blur-[18px] bg-[conic-gradient(from_var(--angle),var(--color-tpc09-c1),var(--color-tpc09-c2),var(--color-tpc09-c3),var(--color-tpc09-c1))] animate-tpc09-spin [animation-play-state:paused] transition-opacity duration-400 group-hover:opacity-70 group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<div class="rounded-[18px] overflow-hidden bg-tpc09-face">
<div role="img" aria-label="Product photo: Flux automatic watch" class="aspect-[16/11] bg-linear-[135deg,var(--color-tpc09-ph),var(--color-tpc09-ph2)] bg-[url('https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&h=550&fit=crop&q=70')] bg-cover bg-center"></div>
<div class="px-5.5 pt-5 pb-5.5">
<p class="font-tpc09-mono text-[11px] tracking-[0.2em] uppercase text-tpc09-c2">Flux</p>
<h3 class="mt-1.5 text-[18px] font-semibold text-tpc09-ink text-pretty">Flux Automatic Watch</h3>
<div class="mt-4.5 flex items-center justify-between gap-3">
<p class="text-[22px] font-extrabold text-tpc09-hi tabular-nums">$460</p>
<button type="button" class="min-h-11 h-11 px-5.5 rounded-xl border-0 bg-linear-[120deg,var(--color-tpc09-c1),var(--color-tpc09-c2)] text-tpc09-onacc text-sm font-bold cursor-pointer transition-[filter,scale] duration-200 hover:brightness-110 active:scale-97 focus-visible:outline-3 focus-visible:outline-tpc09-c1 focus-visible:outline-offset-2 motion-reduce:transition-none">Add to Cart</button>
</div>
</div>
</div>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpc09-face: #0b0a11;
--color-tpc09-face: oklch(0.13 0.012 300);
--color-tpc09-ph: #171522;
--color-tpc09-ph: oklch(0.19 0.018 300);
--color-tpc09-ph2: #0f0e18;
--color-tpc09-ph2: oklch(0.15 0.015 300);
--color-tpc09-ink: #f1eefb;
--color-tpc09-ink: oklch(0.95 0.01 300);
--color-tpc09-hi: #ffffff;
--color-tpc09-hi: oklch(1 0 0);
--color-tpc09-onacc: #0a0713;
--color-tpc09-onacc: oklch(0.13 0.02 300);
--color-tpc09-c1: #6fd8ee;
--color-tpc09-c1: oklch(0.82 0.16 200);
--color-tpc09-c2: #e055b0;
--color-tpc09-c2: oklch(0.72 0.24 330);
--color-tpc09-c3: #f2c94c;
--color-tpc09-c3: oklch(0.85 0.18 90);
--font-tpc09-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc09-mono: 'JetBrains Mono', ui-monospace, monospace;
/* An @theme --animate-* entry mints a real utility: animate-tpc09-spin. */
--animate-tpc09-spin: tpc-09-spin 6s linear infinite;
}
/* The registered angle: unregistered, --angle is a string and will not
interpolate, so the border would jump rather than sweep. */
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes tpc-09-spin {
to {
--angle: 360deg;
}
}
.tpc-09 {
width: 100%;
min-height: 100svh;
}
.tpc-09 ::selection {
background: var(--color-tpc09-c2);
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 Neon Glowing Conic-Border Product Card
Source: https://codefronts.com/components/tailwind-css-product-cards/tailwind-neon-glowing-conic-border-product-card/
A dark card wrapped in a conic-gradient border that ignites and spins on hover, with a blurred copy of itself throwing the glow. The rotation needs a registered @property --angle and a @keyframes — both stylesheet rules — but the animation itself becomes a real utility, because --animate-tpc09-spin in @theme mints animate-tpc09-spin.
## HTML
```html
<section class="tpc-09 w-full min-h-svh grid place-items-center font-tpc09-sans px-5 py-14 bg-[radial-gradient(120%_120%_at_50%_20%,oklch(0.16_0.02_300),oklch(0.09_0.01_300))]" aria-label="Neon glowing border product card demo">
<article class="group relative isolate w-80 max-w-full p-0.5 rounded-[20px]">
<span aria-hidden="true" class="absolute inset-0 -z-10 rounded-[20px] opacity-50 bg-[conic-gradient(from_var(--angle),var(--color-tpc09-c1),var(--color-tpc09-c2),var(--color-tpc09-c3),var(--color-tpc09-c1))] animate-tpc09-spin [animation-play-state:paused] transition-opacity duration-400 group-hover:opacity-100 group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<span aria-hidden="true" class="absolute inset-0 -z-10 rounded-[20px] opacity-25 blur-[18px] bg-[conic-gradient(from_var(--angle),var(--color-tpc09-c1),var(--color-tpc09-c2),var(--color-tpc09-c3),var(--color-tpc09-c1))] animate-tpc09-spin [animation-play-state:paused] transition-opacity duration-400 group-hover:opacity-70 group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<div class="rounded-[18px] overflow-hidden bg-tpc09-face">
<div role="img" aria-label="Product photo: Flux automatic watch" class="aspect-[16/11] bg-linear-[135deg,var(--color-tpc09-ph),var(--color-tpc09-ph2)] bg-[url('https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&h=550&fit=crop&q=70')] bg-cover bg-center"></div>
<div class="px-5.5 pt-5 pb-5.5">
<p class="font-tpc09-mono text-[11px] tracking-[0.2em] uppercase text-tpc09-c2">Flux</p>
<h3 class="mt-1.5 text-[18px] font-semibold text-tpc09-ink text-pretty">Flux Automatic Watch</h3>
<div class="mt-4.5 flex items-center justify-between gap-3">
<p class="text-[22px] font-extrabold text-tpc09-hi tabular-nums">$460</p>
<button type="button" class="min-h-11 h-11 px-5.5 rounded-xl border-0 bg-linear-[120deg,var(--color-tpc09-c1),var(--color-tpc09-c2)] text-tpc09-onacc text-sm font-bold cursor-pointer transition-[filter,scale] duration-200 hover:brightness-110 active:scale-97 focus-visible:outline-3 focus-visible:outline-tpc09-c1 focus-visible:outline-offset-2 motion-reduce:transition-none">Add to Cart</button>
</div>
</div>
</div>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpc09-face: #0b0a11;
--color-tpc09-face: oklch(0.13 0.012 300);
--color-tpc09-ph: #171522;
--color-tpc09-ph: oklch(0.19 0.018 300);
--color-tpc09-ph2: #0f0e18;
--color-tpc09-ph2: oklch(0.15 0.015 300);
--color-tpc09-ink: #f1eefb;
--color-tpc09-ink: oklch(0.95 0.01 300);
--color-tpc09-hi: #ffffff;
--color-tpc09-hi: oklch(1 0 0);
--color-tpc09-onacc: #0a0713;
--color-tpc09-onacc: oklch(0.13 0.02 300);
--color-tpc09-c1: #6fd8ee;
--color-tpc09-c1: oklch(0.82 0.16 200);
--color-tpc09-c2: #e055b0;
--color-tpc09-c2: oklch(0.72 0.24 330);
--color-tpc09-c3: #f2c94c;
--color-tpc09-c3: oklch(0.85 0.18 90);
--font-tpc09-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc09-mono: 'JetBrains Mono', ui-monospace, monospace;
/* An @theme --animate-* entry mints a real utility: animate-tpc09-spin. */
--animate-tpc09-spin: tpc-09-spin 6s linear infinite;
}
/* The registered angle: unregistered, --angle is a string and will not
interpolate, so the border would jump rather than sweep. */
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes tpc-09-spin {
to {
--angle: 360deg;
}
}
.tpc-09 {
width: 100%;
min-height: 100svh;
}
.tpc-09 ::selection {
background: var(--color-tpc09-c2);
color: #fff;
}
```How this works
The animation is a theme token. This is the neatest v4 win in the collection:
@theme {
--animate-tpc09-spin: tpc-09-spin 6s linear infinite;
}
@keyframes tpc-09-spin { to { --angle: 360deg } }The keyframes stay in the stylesheet — they have to, they are a rule — but animate-tpc09-spin is now a utility you can put on any element and pair with variants like motion-reduce:animate-none.
Why @property is mandatory. An unregistered --angle is a string, and strings do not interpolate: the border would jump from 0deg to 360deg with nothing in between. Registering it as <angle> is what makes the sweep smooth, and it is the reason this pattern has a 2022 floor rather than a 2018 one.
Two layers, one gradient. The crisp layer is the border; the same gradient with blur-[18px] is the glow. They share the same --angle, so they rotate in lockstep — which is what makes the glow read as light coming off the border rather than a second effect.
Make it yours
- Neon hues:
--color-tpc09-c1/c2/c3in@theme. Dual-tone (cyan/magenta) reads cyberpunk; a full spectrum reads launch-day. - Speed: the
6sin--animate-tpc09-spin. Under 3s it reads frantic. - Border thickness: the card's
p-0.5— that padding IS the border width. - Always-on: delete the
[animation-play-state:paused]utility and the card is permanently lit. - Glow size:
blur-[18px]on the second layer, plus its hover opacity.
Gotchas — read before shipping
- Animating a custom property requires
@propertywith a typed syntax. Without it you get a static gradient, not a broken card — document it as the fallback. - The card face must be fully opaque above the gradient layers, or the border colour bleeds through the content.
isolateon the card is what makes-z-10on the layers stay inside it. Without it the glow can slide under the page background.- A spinning gradient plus an 18px blur is paint-heavy. One or two feature cards, never a grid.
- The paused/running trick needs the animation declared at rest —
group-hover:animate-tpc09-spinalone would restart the sweep from 0deg on every hover.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
@property-driven gradient animation: Chrome 85+, Safari 16.4+, Firefox 128+. Where @property is missing the border shows a static gradient and the card is otherwise unaffected.