08 / 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 Chroma Conic Gradient Border Spin Button
A dark premium CTA wrapped in a razor-thin conic-gradient border that spins continuously like a rotating light source — the AI/fintech 'live' look. This is the demo where the port is most honest about its limits: @property is what makes an angle animatable at all, and it has no utility form.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tbh-08-group w-full min-h-svh flex flex-wrap">
<section class="tbh-08a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh08-sans bg-tbh08a-stage" aria-label="Conic border spin button">
<button type="button" class="relative isolate cursor-pointer border-0 rounded-[14px] px-[38px] py-4 text-base font-bold text-white bg-tbh08a-card transition-[translate] duration-200 before:content-[''] before:absolute before:-inset-0.5 before:z-0 before:rounded-2xl before:bg-[conic-gradient(from_var(--tbh08a-angle),var(--color-tbh08a-c1),var(--color-tbh08a-c2),var(--color-tbh08a-c3),var(--color-tbh08a-c1))] before:animate-tbh08a-spin after:content-[''] after:absolute after:inset-0 after:z-0 after:rounded-[14px] after:bg-tbh08a-card hover:-translate-y-0.5 hover:before:[animation-duration:1.6s] hover:before:[filter:brightness(1.25)_saturate(1.2)] focus-visible:-translate-y-0.5 focus-visible:before:[animation-duration:1.6s] focus-visible:outline-3 focus-visible:outline-tbh08a-c1 focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span class="relative z-[1]">Launch app</span>
</button>
</section>
<section class="tbh-08b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh08-sans bg-tbh08b-stage" aria-label="Dual arc border button">
<button type="button" class="relative isolate cursor-pointer border-0 rounded-full px-10 py-4 text-base font-bold text-tbh08b-ink bg-tbh08b-card transition-[translate] duration-200 before:content-[''] before:absolute before:-inset-0.5 before:z-0 before:rounded-full before:bg-[conic-gradient(from_var(--tbh08b-angle),transparent_0_25%,var(--color-tbh08b-arc)_35%_40%,transparent_50%_75%,var(--color-tbh08b-arc)_85%_90%,transparent)] before:animate-tbh08b-spin after:content-[''] after:absolute after:inset-0 after:z-0 after:rounded-full after:bg-tbh08b-card hover:-translate-y-0.5 hover:before:[animation-duration:1.2s] focus-visible:-translate-y-0.5 focus-visible:before:[animation-duration:1.2s] focus-visible:outline-3 focus-visible:outline-tbh08b-arc focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span class="relative z-[1]">Connect wallet</span>
</button>
</section>
</div><div class="tbh-08-group w-full min-h-svh flex flex-wrap">
<section class="tbh-08a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh08-sans bg-tbh08a-stage" aria-label="Conic border spin button">
<button type="button" class="relative isolate cursor-pointer border-0 rounded-[14px] px-[38px] py-4 text-base font-bold text-white bg-tbh08a-card transition-[translate] duration-200 before:content-[''] before:absolute before:-inset-0.5 before:z-0 before:rounded-2xl before:bg-[conic-gradient(from_var(--tbh08a-angle),var(--color-tbh08a-c1),var(--color-tbh08a-c2),var(--color-tbh08a-c3),var(--color-tbh08a-c1))] before:animate-tbh08a-spin after:content-[''] after:absolute after:inset-0 after:z-0 after:rounded-[14px] after:bg-tbh08a-card hover:-translate-y-0.5 hover:before:[animation-duration:1.6s] hover:before:[filter:brightness(1.25)_saturate(1.2)] focus-visible:-translate-y-0.5 focus-visible:before:[animation-duration:1.6s] focus-visible:outline-3 focus-visible:outline-tbh08a-c1 focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span class="relative z-[1]">Launch app</span>
</button>
</section>
<section class="tbh-08b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh08-sans bg-tbh08b-stage" aria-label="Dual arc border button">
<button type="button" class="relative isolate cursor-pointer border-0 rounded-full px-10 py-4 text-base font-bold text-tbh08b-ink bg-tbh08b-card transition-[translate] duration-200 before:content-[''] before:absolute before:-inset-0.5 before:z-0 before:rounded-full before:bg-[conic-gradient(from_var(--tbh08b-angle),transparent_0_25%,var(--color-tbh08b-arc)_35%_40%,transparent_50%_75%,var(--color-tbh08b-arc)_85%_90%,transparent)] before:animate-tbh08b-spin after:content-[''] after:absolute after:inset-0 after:z-0 after:rounded-full after:bg-tbh08b-card hover:-translate-y-0.5 hover:before:[animation-duration:1.2s] focus-visible:-translate-y-0.5 focus-visible:before:[animation-duration:1.2s] focus-visible:outline-3 focus-visible:outline-tbh08b-arc focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span class="relative z-[1]">Connect wallet</span>
</button>
</section>
</div>@import "tailwindcss";
/* Stays CSS: a property registration is a type declaration, not a style — there is no
utility form, and the typed syntax is the only thing that makes the angle animatable. */
@property --tbh08a-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@property --tbh08b-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@theme {
--color-tbh08a-card: #12101c;
--color-tbh08a-card: oklch(0.18 0.03 285);
--color-tbh08a-stage: #08070f;
--color-tbh08a-stage: oklch(0.12 0.02 285);
--color-tbh08a-c1: #22e3ff;
--color-tbh08a-c1: oklch(0.85 0.15 210);
--color-tbh08a-c2: #7c5cff;
--color-tbh08a-c2: oklch(0.6 0.22 285);
--color-tbh08a-c3: #ff2bd6;
--color-tbh08a-c3: oklch(0.7 0.28 330);
--color-tbh08b-card: #0c1618;
--color-tbh08b-card: oklch(0.17 0.02 195);
--color-tbh08b-stage: #050b0c;
--color-tbh08b-stage: oklch(0.11 0.015 195);
--color-tbh08b-arc: #2ff5c8;
--color-tbh08b-arc: oklch(0.85 0.17 170);
--color-tbh08b-ink: #eafff8;
--color-tbh08b-ink: oklch(0.97 0.02 170);
--font-tbh08-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh08a-spin: tbh08a-spin 4s linear infinite;
--animate-tbh08b-spin: tbh08b-spin 3s linear infinite;
}
/* Stays CSS: keyframes that animate a registered property. */
@keyframes tbh08a-spin {
to {
--tbh08a-angle: 360deg;
}
}
@keyframes tbh08b-spin {
to {
--tbh08b-angle: 360deg;
}
}
.tbh-08-group {
width: 100%;
min-height: 100svh;
}
.tbh-08a,
.tbh-08b {
min-height: 100svh;
}@import "tailwindcss";
/* Stays CSS: a property registration is a type declaration, not a style — there is no
utility form, and the typed syntax is the only thing that makes the angle animatable. */
@property --tbh08a-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@property --tbh08b-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@theme {
--color-tbh08a-card: #12101c;
--color-tbh08a-card: oklch(0.18 0.03 285);
--color-tbh08a-stage: #08070f;
--color-tbh08a-stage: oklch(0.12 0.02 285);
--color-tbh08a-c1: #22e3ff;
--color-tbh08a-c1: oklch(0.85 0.15 210);
--color-tbh08a-c2: #7c5cff;
--color-tbh08a-c2: oklch(0.6 0.22 285);
--color-tbh08a-c3: #ff2bd6;
--color-tbh08a-c3: oklch(0.7 0.28 330);
--color-tbh08b-card: #0c1618;
--color-tbh08b-card: oklch(0.17 0.02 195);
--color-tbh08b-stage: #050b0c;
--color-tbh08b-stage: oklch(0.11 0.015 195);
--color-tbh08b-arc: #2ff5c8;
--color-tbh08b-arc: oklch(0.85 0.17 170);
--color-tbh08b-ink: #eafff8;
--color-tbh08b-ink: oklch(0.97 0.02 170);
--font-tbh08-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh08a-spin: tbh08a-spin 4s linear infinite;
--animate-tbh08b-spin: tbh08b-spin 3s linear infinite;
}
/* Stays CSS: keyframes that animate a registered property. */
@keyframes tbh08a-spin {
to {
--tbh08a-angle: 360deg;
}
}
@keyframes tbh08b-spin {
to {
--tbh08b-angle: 360deg;
}
}
.tbh-08-group {
width: 100%;
min-height: 100svh;
}
.tbh-08a,
.tbh-08b {
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";
/* Stays CSS: a property registration is a type declaration, not a style — there is no
utility form, and the typed syntax is the only thing that makes the angle animatable. */
@property --tbh08a-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@property --tbh08b-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@theme {
--color-tbh08a-card: #12101c;
--color-tbh08a-card: oklch(0.18 0.03 285);
--color-tbh08a-stage: #08070f;
--color-tbh08a-stage: oklch(0.12 0.02 285);
--color-tbh08a-c1: #22e3ff;
--color-tbh08a-c1: oklch(0.85 0.15 210);
--color-tbh08a-c2: #7c5cff;
--color-tbh08a-c2: oklch(0.6 0.22 285);
--color-tbh08a-c3: #ff2bd6;
--color-tbh08a-c3: oklch(0.7 0.28 330);
--color-tbh08b-card: #0c1618;
--color-tbh08b-card: oklch(0.17 0.02 195);
--color-tbh08b-stage: #050b0c;
--color-tbh08b-stage: oklch(0.11 0.015 195);
--color-tbh08b-arc: #2ff5c8;
--color-tbh08b-arc: oklch(0.85 0.17 170);
--color-tbh08b-ink: #eafff8;
--color-tbh08b-ink: oklch(0.97 0.02 170);
--font-tbh08-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh08a-spin: tbh08a-spin 4s linear infinite;
--animate-tbh08b-spin: tbh08b-spin 3s linear infinite;
}
/* Stays CSS: keyframes that animate a registered property. */
@keyframes tbh08a-spin {
to {
--tbh08a-angle: 360deg;
}
}
@keyframes tbh08b-spin {
to {
--tbh08b-angle: 360deg;
}
}
.tbh-08-group {
width: 100%;
min-height: 100svh;
}
.tbh-08a,
.tbh-08b {
min-height: 100svh;
}@import "tailwindcss";
/* Stays CSS: a property registration is a type declaration, not a style — there is no
utility form, and the typed syntax is the only thing that makes the angle animatable. */
@property --tbh08a-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@property --tbh08b-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@theme {
--color-tbh08a-card: #12101c;
--color-tbh08a-card: oklch(0.18 0.03 285);
--color-tbh08a-stage: #08070f;
--color-tbh08a-stage: oklch(0.12 0.02 285);
--color-tbh08a-c1: #22e3ff;
--color-tbh08a-c1: oklch(0.85 0.15 210);
--color-tbh08a-c2: #7c5cff;
--color-tbh08a-c2: oklch(0.6 0.22 285);
--color-tbh08a-c3: #ff2bd6;
--color-tbh08a-c3: oklch(0.7 0.28 330);
--color-tbh08b-card: #0c1618;
--color-tbh08b-card: oklch(0.17 0.02 195);
--color-tbh08b-stage: #050b0c;
--color-tbh08b-stage: oklch(0.11 0.015 195);
--color-tbh08b-arc: #2ff5c8;
--color-tbh08b-arc: oklch(0.85 0.17 170);
--color-tbh08b-ink: #eafff8;
--color-tbh08b-ink: oklch(0.97 0.02 170);
--font-tbh08-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh08a-spin: tbh08a-spin 4s linear infinite;
--animate-tbh08b-spin: tbh08b-spin 3s linear infinite;
}
/* Stays CSS: keyframes that animate a registered property. */
@keyframes tbh08a-spin {
to {
--tbh08a-angle: 360deg;
}
}
@keyframes tbh08b-spin {
to {
--tbh08b-angle: 360deg;
}
}
.tbh-08-group {
width: 100%;
min-height: 100svh;
}
.tbh-08a,
.tbh-08b {
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 batch's clearest example of the correct split:
@propertyand@keyframesstay CSS because they are declarations about types and time, while the gradient, the mask geometry, the hover speed-up and the stop-on-reduced-motion are utilities on the element. - Promoting the gradient stops to
@themecolours pays off immediately: the focus ring readsoutline-tbh08a-c1, so the ring is literally the same cyan the rim starts on — in the CSS original that hex was typed twice. - UI change from the CSS original: the hover speed-up and brightness lift now also fire on
focus-visible:, and the focus ring colour is drawn from the rim palette instead of a hard-coded hex.
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 Chroma Conic Gradient Border Spin Button
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-chroma-conic-gradient-border-spin-button/
A dark premium CTA wrapped in a razor-thin conic-gradient border that spins continuously like a rotating light source — the AI/fintech 'live' look. This is the demo where the port is most honest about its limits: @property is what makes an angle animatable at all, and it has no utility form.
## HTML
```html
<div class="tbh-08-group w-full min-h-svh flex flex-wrap">
<section class="tbh-08a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh08-sans bg-tbh08a-stage" aria-label="Conic border spin button">
<button type="button" class="relative isolate cursor-pointer border-0 rounded-[14px] px-[38px] py-4 text-base font-bold text-white bg-tbh08a-card transition-[translate] duration-200 before:content-[''] before:absolute before:-inset-0.5 before:z-0 before:rounded-2xl before:bg-[conic-gradient(from_var(--tbh08a-angle),var(--color-tbh08a-c1),var(--color-tbh08a-c2),var(--color-tbh08a-c3),var(--color-tbh08a-c1))] before:animate-tbh08a-spin after:content-[''] after:absolute after:inset-0 after:z-0 after:rounded-[14px] after:bg-tbh08a-card hover:-translate-y-0.5 hover:before:[animation-duration:1.6s] hover:before:[filter:brightness(1.25)_saturate(1.2)] focus-visible:-translate-y-0.5 focus-visible:before:[animation-duration:1.6s] focus-visible:outline-3 focus-visible:outline-tbh08a-c1 focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span class="relative z-[1]">Launch app</span>
</button>
</section>
<section class="tbh-08b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh08-sans bg-tbh08b-stage" aria-label="Dual arc border button">
<button type="button" class="relative isolate cursor-pointer border-0 rounded-full px-10 py-4 text-base font-bold text-tbh08b-ink bg-tbh08b-card transition-[translate] duration-200 before:content-[''] before:absolute before:-inset-0.5 before:z-0 before:rounded-full before:bg-[conic-gradient(from_var(--tbh08b-angle),transparent_0_25%,var(--color-tbh08b-arc)_35%_40%,transparent_50%_75%,var(--color-tbh08b-arc)_85%_90%,transparent)] before:animate-tbh08b-spin after:content-[''] after:absolute after:inset-0 after:z-0 after:rounded-full after:bg-tbh08b-card hover:-translate-y-0.5 hover:before:[animation-duration:1.2s] focus-visible:-translate-y-0.5 focus-visible:before:[animation-duration:1.2s] focus-visible:outline-3 focus-visible:outline-tbh08b-arc focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span class="relative z-[1]">Connect wallet</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
/* Stays CSS: a property registration is a type declaration, not a style — there is no
utility form, and the typed syntax is the only thing that makes the angle animatable. */
@property --tbh08a-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@property --tbh08b-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@theme {
--color-tbh08a-card: #12101c;
--color-tbh08a-card: oklch(0.18 0.03 285);
--color-tbh08a-stage: #08070f;
--color-tbh08a-stage: oklch(0.12 0.02 285);
--color-tbh08a-c1: #22e3ff;
--color-tbh08a-c1: oklch(0.85 0.15 210);
--color-tbh08a-c2: #7c5cff;
--color-tbh08a-c2: oklch(0.6 0.22 285);
--color-tbh08a-c3: #ff2bd6;
--color-tbh08a-c3: oklch(0.7 0.28 330);
--color-tbh08b-card: #0c1618;
--color-tbh08b-card: oklch(0.17 0.02 195);
--color-tbh08b-stage: #050b0c;
--color-tbh08b-stage: oklch(0.11 0.015 195);
--color-tbh08b-arc: #2ff5c8;
--color-tbh08b-arc: oklch(0.85 0.17 170);
--color-tbh08b-ink: #eafff8;
--color-tbh08b-ink: oklch(0.97 0.02 170);
--font-tbh08-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh08a-spin: tbh08a-spin 4s linear infinite;
--animate-tbh08b-spin: tbh08b-spin 3s linear infinite;
}
/* Stays CSS: keyframes that animate a registered property. */
@keyframes tbh08a-spin {
to {
--tbh08a-angle: 360deg;
}
}
@keyframes tbh08b-spin {
to {
--tbh08b-angle: 360deg;
}
}
.tbh-08-group {
width: 100%;
min-height: 100svh;
}
.tbh-08a,
.tbh-08b {
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 Chroma Conic Gradient Border Spin Button
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-chroma-conic-gradient-border-spin-button/
A dark premium CTA wrapped in a razor-thin conic-gradient border that spins continuously like a rotating light source — the AI/fintech 'live' look. This is the demo where the port is most honest about its limits: @property is what makes an angle animatable at all, and it has no utility form.
## HTML
```html
<div class="tbh-08-group w-full min-h-svh flex flex-wrap">
<section class="tbh-08a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh08-sans bg-tbh08a-stage" aria-label="Conic border spin button">
<button type="button" class="relative isolate cursor-pointer border-0 rounded-[14px] px-[38px] py-4 text-base font-bold text-white bg-tbh08a-card transition-[translate] duration-200 before:content-[''] before:absolute before:-inset-0.5 before:z-0 before:rounded-2xl before:bg-[conic-gradient(from_var(--tbh08a-angle),var(--color-tbh08a-c1),var(--color-tbh08a-c2),var(--color-tbh08a-c3),var(--color-tbh08a-c1))] before:animate-tbh08a-spin after:content-[''] after:absolute after:inset-0 after:z-0 after:rounded-[14px] after:bg-tbh08a-card hover:-translate-y-0.5 hover:before:[animation-duration:1.6s] hover:before:[filter:brightness(1.25)_saturate(1.2)] focus-visible:-translate-y-0.5 focus-visible:before:[animation-duration:1.6s] focus-visible:outline-3 focus-visible:outline-tbh08a-c1 focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span class="relative z-[1]">Launch app</span>
</button>
</section>
<section class="tbh-08b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh08-sans bg-tbh08b-stage" aria-label="Dual arc border button">
<button type="button" class="relative isolate cursor-pointer border-0 rounded-full px-10 py-4 text-base font-bold text-tbh08b-ink bg-tbh08b-card transition-[translate] duration-200 before:content-[''] before:absolute before:-inset-0.5 before:z-0 before:rounded-full before:bg-[conic-gradient(from_var(--tbh08b-angle),transparent_0_25%,var(--color-tbh08b-arc)_35%_40%,transparent_50%_75%,var(--color-tbh08b-arc)_85%_90%,transparent)] before:animate-tbh08b-spin after:content-[''] after:absolute after:inset-0 after:z-0 after:rounded-full after:bg-tbh08b-card hover:-translate-y-0.5 hover:before:[animation-duration:1.2s] focus-visible:-translate-y-0.5 focus-visible:before:[animation-duration:1.2s] focus-visible:outline-3 focus-visible:outline-tbh08b-arc focus-visible:outline-offset-4 motion-reduce:before:animate-none motion-reduce:hover:translate-y-0">
<span class="relative z-[1]">Connect wallet</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
/* Stays CSS: a property registration is a type declaration, not a style — there is no
utility form, and the typed syntax is the only thing that makes the angle animatable. */
@property --tbh08a-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@property --tbh08b-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@theme {
--color-tbh08a-card: #12101c;
--color-tbh08a-card: oklch(0.18 0.03 285);
--color-tbh08a-stage: #08070f;
--color-tbh08a-stage: oklch(0.12 0.02 285);
--color-tbh08a-c1: #22e3ff;
--color-tbh08a-c1: oklch(0.85 0.15 210);
--color-tbh08a-c2: #7c5cff;
--color-tbh08a-c2: oklch(0.6 0.22 285);
--color-tbh08a-c3: #ff2bd6;
--color-tbh08a-c3: oklch(0.7 0.28 330);
--color-tbh08b-card: #0c1618;
--color-tbh08b-card: oklch(0.17 0.02 195);
--color-tbh08b-stage: #050b0c;
--color-tbh08b-stage: oklch(0.11 0.015 195);
--color-tbh08b-arc: #2ff5c8;
--color-tbh08b-arc: oklch(0.85 0.17 170);
--color-tbh08b-ink: #eafff8;
--color-tbh08b-ink: oklch(0.97 0.02 170);
--font-tbh08-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tbh08a-spin: tbh08a-spin 4s linear infinite;
--animate-tbh08b-spin: tbh08b-spin 3s linear infinite;
}
/* Stays CSS: keyframes that animate a registered property. */
@keyframes tbh08a-spin {
to {
--tbh08a-angle: 360deg;
}
}
@keyframes tbh08b-spin {
to {
--tbh08b-angle: 360deg;
}
}
.tbh-08-group {
width: 100%;
min-height: 100svh;
}
.tbh-08a,
.tbh-08b {
min-height: 100svh;
}
```How this works
What has to stay CSS. A bare custom property is a string, and strings do not interpolate — @property with syntax:'<angle>' is what lets the keyframe tween 0deg → 360deg instead of snapping. Tailwind has no utility for a property registration, and should not: it is a type declaration, not a style.
Everything around it is utilities. The gradient, the sandwich geometry, the rounding, the hover speed-up and the reduced-motion stop all live in the class list, which is the part that used to be ten selectors across two rules.
The rim is a masked layer, not a border. before:-inset-0.5 makes the gradient 2px larger than the card on every side; after:inset-0 with the card colour hides all but that ring. Both pseudos sit at z-0 under a relative z-[1] label, which is why the button carries isolate.
Make it yours
- Rim palette: the conic stops. Design A runs a full spectrum, design B two arcs on transparent — the second reads as a scanning sweep rather than a rainbow.
- Speed: the
4s/3sin the@themeanimation tokens, and thehover:before:[animation-duration:...]twin. - Rim thickness:
before:-inset-0.5(2px) →before:-inset-pxfor a hairline. - Card colour:
--color-tbh08a-card, read by both the button and the maskingafter:layer — they must match exactly or a seam appears.
Gotchas — read before shipping
@propertymust be registered with the exact namespaced name the gradient reads, and it is document-global — two demos on one page need two names, which is why these are--tbh08a-angleand--tbh08b-angle.- The masking
after:layer must be fully opaque. A translucent card colour lets the gradient bleed through and the 'thin rim' becomes a wash. - The pseudos need a slightly larger radius than the card (
before:rounded-2xlagainstrounded-[14px]), or the rim pinches at the corners. - Without
@propertysupport the angle never animates and the rim renders static — acceptable, and the reason the gradient's resting position is still attractive.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
@property is the gate (Chrome 85, Safari 16.4, Firefox 128) and it is also Tailwind v4's own floor, so anything that renders the generated sheet can animate the angle.