29 / 36
All 36 in 36 Tailwind CSS Button Hover Effects
- 01Tailwind Magnetic Ripple Attraction Button Hover Effect
- 02Tailwind Cyberpunk Holographic Glitch Grid Button
- 03Tailwind Frosted Glassmorphism Shimmer Glow Button
- 04Tailwind Organic Blob Morph Button Hover Effect
- 05Tailwind Dual-Line Intersection Border Draw Button
- 06Tailwind Isometric Tactile 3D Depress Button
- 07Tailwind Infinite Sliding Kinetic Marquee Button
- 08Tailwind Chroma Conic Gradient Border Spin Button
- 09Tailwind Minimalist Border & Outline Drawing Hover Effects
- 10Tailwind Modern 3D Skeuomorphic Button Press Effects
- 11Tailwind Neo-Brutalist & Cyberpunk Button Glow Effects
- 12Tailwind Sliding Background Color Wipe Button Effects
- 13Tailwind Liquid Fill & Organic Morphing Button Effects
- 14Tailwind Kinetic Text Flip & Icon Reveal Button Effects
- 15Tailwind Liquid Wave Fill Button Hover Effect
- 16Tailwind Magnetic Border Drift Button
- 17Tailwind Glitch Slice Button Hover Effect
- 18Tailwind Neon Pulse Glow Button Hover Effect
- 19Tailwind Split Reveal Curtain Button Effect
- 20Tailwind Diagonal Shutter Sweep Button Effect
- 21Tailwind Elastic Lift Spring Button Hover Effect
- 22Tailwind Center Underline Draw Link Button Effects
- 23Tailwind 3D Flip Card Button Hover Effect
- 24Tailwind Particle Burst Confetti Button Effect
- 25Tailwind Slide Doors Closing Panel Button Effect
- 26Tailwind Typewriter Retype Terminal Button Effect
- 27Tailwind Hard Shadow Shift Flat Button Effect
- 28Tailwind Material Ripple Wave Button Effect
- 29Tailwind Rainbow Gradient Border Spin Button
- 30Tailwind Morph Shape Silhouette Button Effects
- 31Tailwind Icon Slide In Directional Button Effect
- 32Tailwind Cursor Spotlight Glow Button
- 33Tailwind Stagger Fill Equaliser Button Effect
- 34Tailwind Sunburst Expand Radiant Button Effect
- 35Tailwind 3D Press Plunger Key Button Effect
- 36Tailwind Add-to-Cart Liquid Glass Ripple Button
Tailwind Rainbow Gradient Border Spin Button
A vivid CTA framed by a rotating full-spectrum gradient border with a soft blurred glow bleeding behind it — the 'AI / Pro' badge look. Unlike entry 08 it rotates the LAYER rather than a registered angle, so it needs no @property and works further back.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tbh-29-group w-full min-h-svh flex flex-wrap">
<section class="tbh-29a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh29-sans bg-tbh29a-stage" aria-label="Rainbow border spin button">
<div class="group/btn relative isolate overflow-hidden rounded-[22px] p-2">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[150%] aspect-square -z-10 blur-[18px] opacity-55 bg-[conic-gradient(var(--color-tbh29a-c1),var(--color-tbh29a-c2),var(--color-tbh29a-c3),var(--color-tbh29a-c4),var(--color-tbh29a-c5),var(--color-tbh29a-c6),var(--color-tbh29a-c1))] animate-tbh29a-spin transition-opacity duration-300 group-hover/btn:opacity-85 group-hover/btn:[animation-duration:1.4s] motion-reduce:animate-none"></span>
<button type="button" class="relative isolate overflow-hidden cursor-pointer border-0 rounded-[14px] px-[38px] py-4 text-base font-extrabold text-white bg-tbh29a-card transition-[translate] duration-200 before:content-[''] before:absolute before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-[150%] before:aspect-square before:z-0 before:bg-[conic-gradient(var(--color-tbh29a-c1),var(--color-tbh29a-c2),var(--color-tbh29a-c3),var(--color-tbh29a-c4),var(--color-tbh29a-c5),var(--color-tbh29a-c6),var(--color-tbh29a-c1))] before:animate-tbh29a-spin hover:-translate-y-0.5 hover:before:[animation-duration:1.4s] focus-visible:-translate-y-0.5 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="absolute inset-0.5 z-[1] rounded-xl bg-tbh29a-card"></span>
<span class="relative z-[2]">Upgrade to Pro</span>
</button>
</div>
</section>
<section class="tbh-29b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh29-sans bg-tbh29b-stage" aria-label="Duotone border spin button">
<button type="button" class="relative isolate overflow-hidden cursor-pointer border-0 rounded-full px-10 py-4 text-base font-extrabold text-white bg-tbh29b-card transition-[translate] duration-200 before:content-[''] before:absolute before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-[160%] before:aspect-square before:z-0 before:bg-[conic-gradient(from_0deg,var(--color-tbh29b-c1),var(--color-tbh29b-c2),var(--color-tbh29b-c1))] before:animate-tbh29b-spin hover:-translate-y-0.5 hover:before:[animation-duration:1.2s] focus-visible:-translate-y-0.5 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="absolute inset-0.5 z-[1] rounded-full bg-tbh29b-card"></span>
<span class="relative z-[2]">Go premium</span>
</button>
</section>
</div><div class="tbh-29-group w-full min-h-svh flex flex-wrap">
<section class="tbh-29a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh29-sans bg-tbh29a-stage" aria-label="Rainbow border spin button">
<div class="group/btn relative isolate overflow-hidden rounded-[22px] p-2">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[150%] aspect-square -z-10 blur-[18px] opacity-55 bg-[conic-gradient(var(--color-tbh29a-c1),var(--color-tbh29a-c2),var(--color-tbh29a-c3),var(--color-tbh29a-c4),var(--color-tbh29a-c5),var(--color-tbh29a-c6),var(--color-tbh29a-c1))] animate-tbh29a-spin transition-opacity duration-300 group-hover/btn:opacity-85 group-hover/btn:[animation-duration:1.4s] motion-reduce:animate-none"></span>
<button type="button" class="relative isolate overflow-hidden cursor-pointer border-0 rounded-[14px] px-[38px] py-4 text-base font-extrabold text-white bg-tbh29a-card transition-[translate] duration-200 before:content-[''] before:absolute before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-[150%] before:aspect-square before:z-0 before:bg-[conic-gradient(var(--color-tbh29a-c1),var(--color-tbh29a-c2),var(--color-tbh29a-c3),var(--color-tbh29a-c4),var(--color-tbh29a-c5),var(--color-tbh29a-c6),var(--color-tbh29a-c1))] before:animate-tbh29a-spin hover:-translate-y-0.5 hover:before:[animation-duration:1.4s] focus-visible:-translate-y-0.5 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="absolute inset-0.5 z-[1] rounded-xl bg-tbh29a-card"></span>
<span class="relative z-[2]">Upgrade to Pro</span>
</button>
</div>
</section>
<section class="tbh-29b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh29-sans bg-tbh29b-stage" aria-label="Duotone border spin button">
<button type="button" class="relative isolate overflow-hidden cursor-pointer border-0 rounded-full px-10 py-4 text-base font-extrabold text-white bg-tbh29b-card transition-[translate] duration-200 before:content-[''] before:absolute before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-[160%] before:aspect-square before:z-0 before:bg-[conic-gradient(from_0deg,var(--color-tbh29b-c1),var(--color-tbh29b-c2),var(--color-tbh29b-c1))] before:animate-tbh29b-spin hover:-translate-y-0.5 hover:before:[animation-duration:1.2s] focus-visible:-translate-y-0.5 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="absolute inset-0.5 z-[1] rounded-full bg-tbh29b-card"></span>
<span class="relative z-[2]">Go premium</span>
</button>
</section>
</div>@import "tailwindcss";
@theme {
--color-tbh29a-card: #12121a;
--color-tbh29a-card: oklch(0.18 0.015 285);
--color-tbh29a-stage: #07070d;
--color-tbh29a-stage: oklch(0.11 0.01 285);
--color-tbh29a-c1: #ff3d81;
--color-tbh29a-c1: oklch(0.68 0.24 0);
--color-tbh29a-c2: #ffb03d;
--color-tbh29a-c2: oklch(0.82 0.17 70);
--color-tbh29a-c3: #f5f13d;
--color-tbh29a-c3: oklch(0.93 0.19 105);
--color-tbh29a-c4: #3dff8b;
--color-tbh29a-c4: oklch(0.89 0.22 150);
--color-tbh29a-c5: #3dc9ff;
--color-tbh29a-c5: oklch(0.8 0.15 225);
--color-tbh29a-c6: #8b3dff;
--color-tbh29a-c6: oklch(0.55 0.28 295);
--color-tbh29b-card: #0e1220;
--color-tbh29b-card: oklch(0.17 0.025 265);
--color-tbh29b-stage: #060810;
--color-tbh29b-stage: oklch(0.1 0.015 265);
--color-tbh29b-c1: #3d7bff;
--color-tbh29b-c1: oklch(0.6 0.22 265);
--color-tbh29b-c2: #b03dff;
--color-tbh29b-c2: oklch(0.58 0.27 305);
--font-tbh29-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh29a-spin: tbh29a-spin 4s linear infinite;
--animate-tbh29b-spin: tbh29b-spin 3s linear infinite;
}
/* Stays CSS: the spin keyframes — and note what they no longer carry. The layers are centred
with -translate-x-1/2 / -translate-y-1/2 on the discrete translate property, so rotation
is all the keyframe owns. In the CSS original every stop restated translate(-50%,-50%). */
@keyframes tbh29a-spin {
to {
rotate: 360deg;
}
}
@keyframes tbh29b-spin {
to {
rotate: 360deg;
}
}
.tbh-29-group {
width: 100%;
min-height: 100svh;
}
.tbh-29a,
.tbh-29b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh29a-card: #12121a;
--color-tbh29a-card: oklch(0.18 0.015 285);
--color-tbh29a-stage: #07070d;
--color-tbh29a-stage: oklch(0.11 0.01 285);
--color-tbh29a-c1: #ff3d81;
--color-tbh29a-c1: oklch(0.68 0.24 0);
--color-tbh29a-c2: #ffb03d;
--color-tbh29a-c2: oklch(0.82 0.17 70);
--color-tbh29a-c3: #f5f13d;
--color-tbh29a-c3: oklch(0.93 0.19 105);
--color-tbh29a-c4: #3dff8b;
--color-tbh29a-c4: oklch(0.89 0.22 150);
--color-tbh29a-c5: #3dc9ff;
--color-tbh29a-c5: oklch(0.8 0.15 225);
--color-tbh29a-c6: #8b3dff;
--color-tbh29a-c6: oklch(0.55 0.28 295);
--color-tbh29b-card: #0e1220;
--color-tbh29b-card: oklch(0.17 0.025 265);
--color-tbh29b-stage: #060810;
--color-tbh29b-stage: oklch(0.1 0.015 265);
--color-tbh29b-c1: #3d7bff;
--color-tbh29b-c1: oklch(0.6 0.22 265);
--color-tbh29b-c2: #b03dff;
--color-tbh29b-c2: oklch(0.58 0.27 305);
--font-tbh29-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh29a-spin: tbh29a-spin 4s linear infinite;
--animate-tbh29b-spin: tbh29b-spin 3s linear infinite;
}
/* Stays CSS: the spin keyframes — and note what they no longer carry. The layers are centred
with -translate-x-1/2 / -translate-y-1/2 on the discrete translate property, so rotation
is all the keyframe owns. In the CSS original every stop restated translate(-50%,-50%). */
@keyframes tbh29a-spin {
to {
rotate: 360deg;
}
}
@keyframes tbh29b-spin {
to {
rotate: 360deg;
}
}
.tbh-29-group {
width: 100%;
min-height: 100svh;
}
.tbh-29a,
.tbh-29b {
min-height: 100svh;
}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-tbh29a-card: #12121a;
--color-tbh29a-card: oklch(0.18 0.015 285);
--color-tbh29a-stage: #07070d;
--color-tbh29a-stage: oklch(0.11 0.01 285);
--color-tbh29a-c1: #ff3d81;
--color-tbh29a-c1: oklch(0.68 0.24 0);
--color-tbh29a-c2: #ffb03d;
--color-tbh29a-c2: oklch(0.82 0.17 70);
--color-tbh29a-c3: #f5f13d;
--color-tbh29a-c3: oklch(0.93 0.19 105);
--color-tbh29a-c4: #3dff8b;
--color-tbh29a-c4: oklch(0.89 0.22 150);
--color-tbh29a-c5: #3dc9ff;
--color-tbh29a-c5: oklch(0.8 0.15 225);
--color-tbh29a-c6: #8b3dff;
--color-tbh29a-c6: oklch(0.55 0.28 295);
--color-tbh29b-card: #0e1220;
--color-tbh29b-card: oklch(0.17 0.025 265);
--color-tbh29b-stage: #060810;
--color-tbh29b-stage: oklch(0.1 0.015 265);
--color-tbh29b-c1: #3d7bff;
--color-tbh29b-c1: oklch(0.6 0.22 265);
--color-tbh29b-c2: #b03dff;
--color-tbh29b-c2: oklch(0.58 0.27 305);
--font-tbh29-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh29a-spin: tbh29a-spin 4s linear infinite;
--animate-tbh29b-spin: tbh29b-spin 3s linear infinite;
}
/* Stays CSS: the spin keyframes — and note what they no longer carry. The layers are centred
with -translate-x-1/2 / -translate-y-1/2 on the discrete translate property, so rotation
is all the keyframe owns. In the CSS original every stop restated translate(-50%,-50%). */
@keyframes tbh29a-spin {
to {
rotate: 360deg;
}
}
@keyframes tbh29b-spin {
to {
rotate: 360deg;
}
}
.tbh-29-group {
width: 100%;
min-height: 100svh;
}
.tbh-29a,
.tbh-29b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh29a-card: #12121a;
--color-tbh29a-card: oklch(0.18 0.015 285);
--color-tbh29a-stage: #07070d;
--color-tbh29a-stage: oklch(0.11 0.01 285);
--color-tbh29a-c1: #ff3d81;
--color-tbh29a-c1: oklch(0.68 0.24 0);
--color-tbh29a-c2: #ffb03d;
--color-tbh29a-c2: oklch(0.82 0.17 70);
--color-tbh29a-c3: #f5f13d;
--color-tbh29a-c3: oklch(0.93 0.19 105);
--color-tbh29a-c4: #3dff8b;
--color-tbh29a-c4: oklch(0.89 0.22 150);
--color-tbh29a-c5: #3dc9ff;
--color-tbh29a-c5: oklch(0.8 0.15 225);
--color-tbh29a-c6: #8b3dff;
--color-tbh29a-c6: oklch(0.55 0.28 295);
--color-tbh29b-card: #0e1220;
--color-tbh29b-card: oklch(0.17 0.025 265);
--color-tbh29b-stage: #060810;
--color-tbh29b-stage: oklch(0.1 0.015 265);
--color-tbh29b-c1: #3d7bff;
--color-tbh29b-c1: oklch(0.6 0.22 265);
--color-tbh29b-c2: #b03dff;
--color-tbh29b-c2: oklch(0.58 0.27 305);
--font-tbh29-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh29a-spin: tbh29a-spin 4s linear infinite;
--animate-tbh29b-spin: tbh29b-spin 3s linear infinite;
}
/* Stays CSS: the spin keyframes — and note what they no longer carry. The layers are centred
with -translate-x-1/2 / -translate-y-1/2 on the discrete translate property, so rotation
is all the keyframe owns. In the CSS original every stop restated translate(-50%,-50%). */
@keyframes tbh29a-spin {
to {
rotate: 360deg;
}
}
@keyframes tbh29b-spin {
to {
rotate: 360deg;
}
}
.tbh-29-group {
width: 100%;
min-height: 100svh;
}
.tbh-29a,
.tbh-29b {
min-height: 100svh;
}<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 keyframes shrank to
to { rotate: 360deg }. The CSS original had to writetransform: translate(-50%,-50%) rotate(360deg)because one property held both the centring and the spin; v4's discrete properties mean the keyframe describes only the motion. - Six gradient stops became six
@themecolours, which is more verbose in the stylesheet and much better in practice: the glow layer reuses the identical gradient by reference, so the rim and its blurred copy can never drift apart. - UI change from the CSS original: the hover lift fires on
focus-visible:too, and design A's glow opacity now rises on hover (0.55 → 0.85) in step with the speed-up rather than staying fixed.
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 Rainbow Gradient Border Spin Button
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-rainbow-gradient-border-spin-button/
A vivid CTA framed by a rotating full-spectrum gradient border with a soft blurred glow bleeding behind it — the 'AI / Pro' badge look. Unlike entry 08 it rotates the LAYER rather than a registered angle, so it needs no @property and works further back.
## HTML
```html
<div class="tbh-29-group w-full min-h-svh flex flex-wrap">
<section class="tbh-29a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh29-sans bg-tbh29a-stage" aria-label="Rainbow border spin button">
<div class="group/btn relative isolate overflow-hidden rounded-[22px] p-2">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[150%] aspect-square -z-10 blur-[18px] opacity-55 bg-[conic-gradient(var(--color-tbh29a-c1),var(--color-tbh29a-c2),var(--color-tbh29a-c3),var(--color-tbh29a-c4),var(--color-tbh29a-c5),var(--color-tbh29a-c6),var(--color-tbh29a-c1))] animate-tbh29a-spin transition-opacity duration-300 group-hover/btn:opacity-85 group-hover/btn:[animation-duration:1.4s] motion-reduce:animate-none"></span>
<button type="button" class="relative isolate overflow-hidden cursor-pointer border-0 rounded-[14px] px-[38px] py-4 text-base font-extrabold text-white bg-tbh29a-card transition-[translate] duration-200 before:content-[''] before:absolute before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-[150%] before:aspect-square before:z-0 before:bg-[conic-gradient(var(--color-tbh29a-c1),var(--color-tbh29a-c2),var(--color-tbh29a-c3),var(--color-tbh29a-c4),var(--color-tbh29a-c5),var(--color-tbh29a-c6),var(--color-tbh29a-c1))] before:animate-tbh29a-spin hover:-translate-y-0.5 hover:before:[animation-duration:1.4s] focus-visible:-translate-y-0.5 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="absolute inset-0.5 z-[1] rounded-xl bg-tbh29a-card"></span>
<span class="relative z-[2]">Upgrade to Pro</span>
</button>
</div>
</section>
<section class="tbh-29b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh29-sans bg-tbh29b-stage" aria-label="Duotone border spin button">
<button type="button" class="relative isolate overflow-hidden cursor-pointer border-0 rounded-full px-10 py-4 text-base font-extrabold text-white bg-tbh29b-card transition-[translate] duration-200 before:content-[''] before:absolute before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-[160%] before:aspect-square before:z-0 before:bg-[conic-gradient(from_0deg,var(--color-tbh29b-c1),var(--color-tbh29b-c2),var(--color-tbh29b-c1))] before:animate-tbh29b-spin hover:-translate-y-0.5 hover:before:[animation-duration:1.2s] focus-visible:-translate-y-0.5 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="absolute inset-0.5 z-[1] rounded-full bg-tbh29b-card"></span>
<span class="relative z-[2]">Go premium</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh29a-card: #12121a;
--color-tbh29a-card: oklch(0.18 0.015 285);
--color-tbh29a-stage: #07070d;
--color-tbh29a-stage: oklch(0.11 0.01 285);
--color-tbh29a-c1: #ff3d81;
--color-tbh29a-c1: oklch(0.68 0.24 0);
--color-tbh29a-c2: #ffb03d;
--color-tbh29a-c2: oklch(0.82 0.17 70);
--color-tbh29a-c3: #f5f13d;
--color-tbh29a-c3: oklch(0.93 0.19 105);
--color-tbh29a-c4: #3dff8b;
--color-tbh29a-c4: oklch(0.89 0.22 150);
--color-tbh29a-c5: #3dc9ff;
--color-tbh29a-c5: oklch(0.8 0.15 225);
--color-tbh29a-c6: #8b3dff;
--color-tbh29a-c6: oklch(0.55 0.28 295);
--color-tbh29b-card: #0e1220;
--color-tbh29b-card: oklch(0.17 0.025 265);
--color-tbh29b-stage: #060810;
--color-tbh29b-stage: oklch(0.1 0.015 265);
--color-tbh29b-c1: #3d7bff;
--color-tbh29b-c1: oklch(0.6 0.22 265);
--color-tbh29b-c2: #b03dff;
--color-tbh29b-c2: oklch(0.58 0.27 305);
--font-tbh29-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh29a-spin: tbh29a-spin 4s linear infinite;
--animate-tbh29b-spin: tbh29b-spin 3s linear infinite;
}
/* Stays CSS: the spin keyframes — and note what they no longer carry. The layers are centred
with -translate-x-1/2 / -translate-y-1/2 on the discrete translate property, so rotation
is all the keyframe owns. In the CSS original every stop restated translate(-50%,-50%). */
@keyframes tbh29a-spin {
to {
rotate: 360deg;
}
}
@keyframes tbh29b-spin {
to {
rotate: 360deg;
}
}
.tbh-29-group {
width: 100%;
min-height: 100svh;
}
.tbh-29a,
.tbh-29b {
min-height: 100svh;
}
```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 Rainbow Gradient Border Spin Button
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-rainbow-gradient-border-spin-button/
A vivid CTA framed by a rotating full-spectrum gradient border with a soft blurred glow bleeding behind it — the 'AI / Pro' badge look. Unlike entry 08 it rotates the LAYER rather than a registered angle, so it needs no @property and works further back.
## HTML
```html
<div class="tbh-29-group w-full min-h-svh flex flex-wrap">
<section class="tbh-29a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh29-sans bg-tbh29a-stage" aria-label="Rainbow border spin button">
<div class="group/btn relative isolate overflow-hidden rounded-[22px] p-2">
<span aria-hidden="true" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[150%] aspect-square -z-10 blur-[18px] opacity-55 bg-[conic-gradient(var(--color-tbh29a-c1),var(--color-tbh29a-c2),var(--color-tbh29a-c3),var(--color-tbh29a-c4),var(--color-tbh29a-c5),var(--color-tbh29a-c6),var(--color-tbh29a-c1))] animate-tbh29a-spin transition-opacity duration-300 group-hover/btn:opacity-85 group-hover/btn:[animation-duration:1.4s] motion-reduce:animate-none"></span>
<button type="button" class="relative isolate overflow-hidden cursor-pointer border-0 rounded-[14px] px-[38px] py-4 text-base font-extrabold text-white bg-tbh29a-card transition-[translate] duration-200 before:content-[''] before:absolute before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-[150%] before:aspect-square before:z-0 before:bg-[conic-gradient(var(--color-tbh29a-c1),var(--color-tbh29a-c2),var(--color-tbh29a-c3),var(--color-tbh29a-c4),var(--color-tbh29a-c5),var(--color-tbh29a-c6),var(--color-tbh29a-c1))] before:animate-tbh29a-spin hover:-translate-y-0.5 hover:before:[animation-duration:1.4s] focus-visible:-translate-y-0.5 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="absolute inset-0.5 z-[1] rounded-xl bg-tbh29a-card"></span>
<span class="relative z-[2]">Upgrade to Pro</span>
</button>
</div>
</section>
<section class="tbh-29b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh29-sans bg-tbh29b-stage" aria-label="Duotone border spin button">
<button type="button" class="relative isolate overflow-hidden cursor-pointer border-0 rounded-full px-10 py-4 text-base font-extrabold text-white bg-tbh29b-card transition-[translate] duration-200 before:content-[''] before:absolute before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-[160%] before:aspect-square before:z-0 before:bg-[conic-gradient(from_0deg,var(--color-tbh29b-c1),var(--color-tbh29b-c2),var(--color-tbh29b-c1))] before:animate-tbh29b-spin hover:-translate-y-0.5 hover:before:[animation-duration:1.2s] focus-visible:-translate-y-0.5 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="absolute inset-0.5 z-[1] rounded-full bg-tbh29b-card"></span>
<span class="relative z-[2]">Go premium</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh29a-card: #12121a;
--color-tbh29a-card: oklch(0.18 0.015 285);
--color-tbh29a-stage: #07070d;
--color-tbh29a-stage: oklch(0.11 0.01 285);
--color-tbh29a-c1: #ff3d81;
--color-tbh29a-c1: oklch(0.68 0.24 0);
--color-tbh29a-c2: #ffb03d;
--color-tbh29a-c2: oklch(0.82 0.17 70);
--color-tbh29a-c3: #f5f13d;
--color-tbh29a-c3: oklch(0.93 0.19 105);
--color-tbh29a-c4: #3dff8b;
--color-tbh29a-c4: oklch(0.89 0.22 150);
--color-tbh29a-c5: #3dc9ff;
--color-tbh29a-c5: oklch(0.8 0.15 225);
--color-tbh29a-c6: #8b3dff;
--color-tbh29a-c6: oklch(0.55 0.28 295);
--color-tbh29b-card: #0e1220;
--color-tbh29b-card: oklch(0.17 0.025 265);
--color-tbh29b-stage: #060810;
--color-tbh29b-stage: oklch(0.1 0.015 265);
--color-tbh29b-c1: #3d7bff;
--color-tbh29b-c1: oklch(0.6 0.22 265);
--color-tbh29b-c2: #b03dff;
--color-tbh29b-c2: oklch(0.58 0.27 305);
--font-tbh29-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh29a-spin: tbh29a-spin 4s linear infinite;
--animate-tbh29b-spin: tbh29b-spin 3s linear infinite;
}
/* Stays CSS: the spin keyframes — and note what they no longer carry. The layers are centred
with -translate-x-1/2 / -translate-y-1/2 on the discrete translate property, so rotation
is all the keyframe owns. In the CSS original every stop restated translate(-50%,-50%). */
@keyframes tbh29a-spin {
to {
rotate: 360deg;
}
}
@keyframes tbh29b-spin {
to {
rotate: 360deg;
}
}
.tbh-29-group {
width: 100%;
min-height: 100svh;
}
.tbh-29a,
.tbh-29b {
min-height: 100svh;
}
```How this works
Rotate the paint, not the angle. Entry 08 animates a registered <angle> inside the gradient; this one rotates an oversized layer that happens to be painted with a gradient. The result looks nearly identical, needs no @property, and is the technique to reach for when support floor matters — the trade is that the layer must be large enough that its corners never enter the button.
The glow is a copy. after: duplicates the layer, blurs it, drops it behind with a negative z-index and lowers its opacity. One extra element would do the same, but a pseudo-element keeps the DOM at one node and the two layers provably in sync (same keyframe, same duration).
The cover is a real span. Both pseudo slots are used, so the inner mask has to be an element — absolute inset-0.5 in the card colour, which is what turns a full gradient into a 2px rim.
Make it yours
- Palette: the conic stops — design A is a six-stop spectrum, B a two-colour duotone that reads far more corporate.
- Speed:
--animate-tbh29a-spin(4s) and the hover override (1.4s). - Rim thickness: the cover's
inset-0.5;inset-pxfor a hairline. - Glow strength:
after:blur-[18px] after:opacity-55, rising tohover:after:opacity-85.
Gotchas — read before shipping
- Oversize the rotating layer (150–160%) or its square corners sweep into view as it turns — the classic failure of this technique.
- The cover span must be fully opaque and exactly the card colour, or the gradient shows through as a wash.
- Pick this OR entry 08 on a given page, not both: two different spinning-border implementations side by side is an inconsistency a reader will notice before a designer does.
- The blurred
after:layer sits at-z-10, which only works because the button carriesisolate— otherwise it paints behind the page background.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Rotating a gradient-painted layer works wherever transforms do; this is the wider-support alternative to entry 08's @property approach.