21 / 30
All 30 in 30 Tailwind CSS Login Forms
- 01Tailwind Split-Screen Brand Hero Login Form
- 02Tailwind Multi-Tenant Workspace Account Switcher
- 03Tailwind Floating Label Login Form with peer Variants
- 04Tailwind Web3 Wallet Connect Login Grid
- 05Tailwind Minimalist Fintech Login Card with Layered Shadow
- 06Tailwind OAuth Social Login Form with Brand SVG Buttons
- 07Tailwind Passwordless Magic Link Login with data-state Machine
- 08Tailwind Aurora Glow Login Card with Drifting Blur Blobs
- 09Tailwind Neon Synthwave Arcade Login Form
- 10Tailwind Sliding Login and Sign Up Panel with peer-checked
- 11Tailwind 3D Flip Login Card with Password Reset Back Face
- 12Tailwind Terminal CLI Prompt Login Form
- 13Tailwind Floating Pill Login Widget Docked Over App Content
- 14Tailwind Gradient Border Login Card with background-clip
- 15Tailwind Slide-Over Side Panel Login without JavaScript
- 16Tailwind Social-First Sign Up with Brand-Compliant Buttons
- 17Tailwind Animated Mascot Login that Covers Its Eyes
- 18Tailwind Accessible Compact Login Modal with Scale-In
- 19Tailwind Glassmorphism Login Card with backdrop-filter
- 20Tailwind Multi-Step Login Wizard with Radio-Driven Track
- 21Tailwind Animated Conic Gradient Border Login Card
- 22Tailwind Secure Vault Login with user-invalid Error State
- 23Tailwind Neo-Brutalist Login Form with Hard Offset Shadow
- 24Tailwind Show Hide Password Toggle with aria-pressed
- 25Tailwind OTP Code Input Grid with Auto-Advance
- 26Tailwind Password Strength Meter with data-score Variants
- 27Tailwind Numeric PIN Pad Login for Kiosks and POS
- 28Tailwind Inline Form Validation with user-valid and user-invalid
- 29Tailwind Particle Constellation Login over Canvas
- 30Tailwind Biometric Face ID Prompt with Pulse Ripples
Tailwind Animated Conic Gradient Border Login Card
A glowing conic light rotates continuously around the card's edge — the fast-moving-tech accent, rendered by animating one registered angle property rather than repainting a gradient. The @property registration is the part utilities cannot express, which makes this the clearest example in the collection of the CSS that must stay CSS.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tlf-21 w-full min-h-svh grid place-items-center bg-tlf21-stage font-tlf21-sans p-6">
<div class="tlf-21-ring relative isolate w-[min(380px,100%)] p-0.5 rounded-[20px] before:content-[''] before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:bg-[conic-gradient(from_var(--tlf-21-spin),oklch(0.7_0.2_20),oklch(0.72_0.18_300),oklch(0.75_0.16_200),oklch(0.78_0.18_130),oklch(0.7_0.2_20))] before:animate-tlf21-spin after:content-[''] after:absolute after:inset-0 after:-z-2 after:rounded-[inherit] after:opacity-70 after:blur-[16px] after:bg-[conic-gradient(from_var(--tlf-21-spin),oklch(0.7_0.2_20),oklch(0.72_0.18_300),oklch(0.75_0.16_200),oklch(0.78_0.18_130),oklch(0.7_0.2_20))] after:animate-tlf21-spin motion-reduce:before:animate-none motion-reduce:after:animate-none">
<form novalidate class="relative flex flex-col gap-3.5 p-8 rounded-[18px] bg-tlf21-card text-tlf21-ink">
<h1 class="text-2xl font-bold tracking-tight">Sign in</h1>
<p class="-mt-2 mb-1 text-[13.5px] text-tlf21-mut">Ship faster with Comet.</p>
<label class="flex flex-col gap-1.5"><span class="text-[13px] font-semibold text-tlf21-label">Email</span>
<input type="email" autocomplete="email" placeholder="you@company.com" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf21-ink bg-tlf21-field border-[1.5px] border-tlf21-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf21-ph focus-visible:outline-none focus-visible:border-tlf21-acc focus-visible:ring-4 focus-visible:ring-tlf21-halo"></label>
<label class="flex flex-col gap-1.5"><span class="text-[13px] font-semibold text-tlf21-label">Password</span>
<input type="password" autocomplete="current-password" placeholder="••••••••" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf21-ink bg-tlf21-field border-[1.5px] border-tlf21-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf21-ph focus-visible:outline-none focus-visible:border-tlf21-acc focus-visible:ring-4 focus-visible:ring-tlf21-halo"></label>
<button type="submit" class="mt-1 h-12 rounded-xl border-0 cursor-pointer text-tlf21-btnink text-[15px] font-bold bg-[linear-gradient(90deg,oklch(0.85_0.15_130),oklch(0.85_0.14_200))] transition-[filter,scale] duration-150 hover:brightness-105 active:scale-[0.985] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-tlf21-ring motion-reduce:transition-none motion-reduce:active:scale-100">Continue</button>
</form>
</div>
</section><section class="tlf-21 w-full min-h-svh grid place-items-center bg-tlf21-stage font-tlf21-sans p-6">
<div class="tlf-21-ring relative isolate w-[min(380px,100%)] p-0.5 rounded-[20px] before:content-[''] before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:bg-[conic-gradient(from_var(--tlf-21-spin),oklch(0.7_0.2_20),oklch(0.72_0.18_300),oklch(0.75_0.16_200),oklch(0.78_0.18_130),oklch(0.7_0.2_20))] before:animate-tlf21-spin after:content-[''] after:absolute after:inset-0 after:-z-2 after:rounded-[inherit] after:opacity-70 after:blur-[16px] after:bg-[conic-gradient(from_var(--tlf-21-spin),oklch(0.7_0.2_20),oklch(0.72_0.18_300),oklch(0.75_0.16_200),oklch(0.78_0.18_130),oklch(0.7_0.2_20))] after:animate-tlf21-spin motion-reduce:before:animate-none motion-reduce:after:animate-none">
<form novalidate class="relative flex flex-col gap-3.5 p-8 rounded-[18px] bg-tlf21-card text-tlf21-ink">
<h1 class="text-2xl font-bold tracking-tight">Sign in</h1>
<p class="-mt-2 mb-1 text-[13.5px] text-tlf21-mut">Ship faster with Comet.</p>
<label class="flex flex-col gap-1.5"><span class="text-[13px] font-semibold text-tlf21-label">Email</span>
<input type="email" autocomplete="email" placeholder="you@company.com" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf21-ink bg-tlf21-field border-[1.5px] border-tlf21-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf21-ph focus-visible:outline-none focus-visible:border-tlf21-acc focus-visible:ring-4 focus-visible:ring-tlf21-halo"></label>
<label class="flex flex-col gap-1.5"><span class="text-[13px] font-semibold text-tlf21-label">Password</span>
<input type="password" autocomplete="current-password" placeholder="••••••••" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf21-ink bg-tlf21-field border-[1.5px] border-tlf21-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf21-ph focus-visible:outline-none focus-visible:border-tlf21-acc focus-visible:ring-4 focus-visible:ring-tlf21-halo"></label>
<button type="submit" class="mt-1 h-12 rounded-xl border-0 cursor-pointer text-tlf21-btnink text-[15px] font-bold bg-[linear-gradient(90deg,oklch(0.85_0.15_130),oklch(0.85_0.14_200))] transition-[filter,scale] duration-150 hover:brightness-105 active:scale-[0.985] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-tlf21-ring motion-reduce:transition-none motion-reduce:active:scale-100">Continue</button>
</form>
</div>
</section>@import "tailwindcss";
@property --tlf-21-spin {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-tlf21-stage: #07080d;
--color-tlf21-stage: oklch(0.12 0.01 270);
--color-tlf21-card: #12141d;
--color-tlf21-card: oklch(0.19 0.014 270);
--color-tlf21-field: #0e1019;
--color-tlf21-field: oklch(0.16 0.014 270);
--color-tlf21-fieldedge: #2a2f3d;
--color-tlf21-fieldedge: oklch(0.3 0.018 270);
--color-tlf21-ink: #eceef6;
--color-tlf21-ink: oklch(0.95 0.006 270);
--color-tlf21-label: #c2c8d9;
--color-tlf21-label: oklch(0.84 0.014 270);
--color-tlf21-mut: #a0a7bb;
--color-tlf21-mut: oklch(0.72 0.018 270);
--color-tlf21-ph: #757d95;
--color-tlf21-ph: oklch(0.58 0.02 270);
--color-tlf21-acc: #b37cf0;
--color-tlf21-acc: oklch(0.72 0.18 300);
--color-tlf21-halo: #d4bcf6;
--color-tlf21-halo: oklch(0.72 0.18 300 / 0.22);
--color-tlf21-ring: #b9e88f;
--color-tlf21-ring: oklch(0.85 0.15 130 / 0.6);
--color-tlf21-btnink: #0a0b10;
--color-tlf21-btnink: oklch(0.15 0.01 270);
--font-tlf21-sans: 'Chivo', system-ui, sans-serif;
--animate-tlf21-spin: tlf-21-spin 5s linear infinite;
}
@keyframes tlf-21-spin {
to {
--tlf-21-spin: 360deg;
}
}
.tlf-21 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}
/* No conic-gradient / @property: a static linear ring instead of a flickering one. */
@supports not (background: conic-gradient(from 0deg, red, red)) {
.tlf-21 .tlf-21-ring::before {
background: linear-gradient(135deg, oklch(0.7 0.2 20), oklch(0.75 0.16 200));
}
.tlf-21 .tlf-21-ring::after {
display: none;
}
}@import "tailwindcss";
@property --tlf-21-spin {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-tlf21-stage: #07080d;
--color-tlf21-stage: oklch(0.12 0.01 270);
--color-tlf21-card: #12141d;
--color-tlf21-card: oklch(0.19 0.014 270);
--color-tlf21-field: #0e1019;
--color-tlf21-field: oklch(0.16 0.014 270);
--color-tlf21-fieldedge: #2a2f3d;
--color-tlf21-fieldedge: oklch(0.3 0.018 270);
--color-tlf21-ink: #eceef6;
--color-tlf21-ink: oklch(0.95 0.006 270);
--color-tlf21-label: #c2c8d9;
--color-tlf21-label: oklch(0.84 0.014 270);
--color-tlf21-mut: #a0a7bb;
--color-tlf21-mut: oklch(0.72 0.018 270);
--color-tlf21-ph: #757d95;
--color-tlf21-ph: oklch(0.58 0.02 270);
--color-tlf21-acc: #b37cf0;
--color-tlf21-acc: oklch(0.72 0.18 300);
--color-tlf21-halo: #d4bcf6;
--color-tlf21-halo: oklch(0.72 0.18 300 / 0.22);
--color-tlf21-ring: #b9e88f;
--color-tlf21-ring: oklch(0.85 0.15 130 / 0.6);
--color-tlf21-btnink: #0a0b10;
--color-tlf21-btnink: oklch(0.15 0.01 270);
--font-tlf21-sans: 'Chivo', system-ui, sans-serif;
--animate-tlf21-spin: tlf-21-spin 5s linear infinite;
}
@keyframes tlf-21-spin {
to {
--tlf-21-spin: 360deg;
}
}
.tlf-21 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}
/* No conic-gradient / @property: a static linear ring instead of a flickering one. */
@supports not (background: conic-gradient(from 0deg, red, red)) {
.tlf-21 .tlf-21-ring::before {
background: linear-gradient(135deg, oklch(0.7 0.2 20), oklch(0.75 0.16 200));
}
.tlf-21 .tlf-21-ring::after {
display: none;
}
}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";
@property --tlf-21-spin {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-tlf21-stage: #07080d;
--color-tlf21-stage: oklch(0.12 0.01 270);
--color-tlf21-card: #12141d;
--color-tlf21-card: oklch(0.19 0.014 270);
--color-tlf21-field: #0e1019;
--color-tlf21-field: oklch(0.16 0.014 270);
--color-tlf21-fieldedge: #2a2f3d;
--color-tlf21-fieldedge: oklch(0.3 0.018 270);
--color-tlf21-ink: #eceef6;
--color-tlf21-ink: oklch(0.95 0.006 270);
--color-tlf21-label: #c2c8d9;
--color-tlf21-label: oklch(0.84 0.014 270);
--color-tlf21-mut: #a0a7bb;
--color-tlf21-mut: oklch(0.72 0.018 270);
--color-tlf21-ph: #757d95;
--color-tlf21-ph: oklch(0.58 0.02 270);
--color-tlf21-acc: #b37cf0;
--color-tlf21-acc: oklch(0.72 0.18 300);
--color-tlf21-halo: #d4bcf6;
--color-tlf21-halo: oklch(0.72 0.18 300 / 0.22);
--color-tlf21-ring: #b9e88f;
--color-tlf21-ring: oklch(0.85 0.15 130 / 0.6);
--color-tlf21-btnink: #0a0b10;
--color-tlf21-btnink: oklch(0.15 0.01 270);
--font-tlf21-sans: 'Chivo', system-ui, sans-serif;
--animate-tlf21-spin: tlf-21-spin 5s linear infinite;
}
@keyframes tlf-21-spin {
to {
--tlf-21-spin: 360deg;
}
}
.tlf-21 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}
/* No conic-gradient / @property: a static linear ring instead of a flickering one. */
@supports not (background: conic-gradient(from 0deg, red, red)) {
.tlf-21 .tlf-21-ring::before {
background: linear-gradient(135deg, oklch(0.7 0.2 20), oklch(0.75 0.16 200));
}
.tlf-21 .tlf-21-ring::after {
display: none;
}
}@import "tailwindcss";
@property --tlf-21-spin {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-tlf21-stage: #07080d;
--color-tlf21-stage: oklch(0.12 0.01 270);
--color-tlf21-card: #12141d;
--color-tlf21-card: oklch(0.19 0.014 270);
--color-tlf21-field: #0e1019;
--color-tlf21-field: oklch(0.16 0.014 270);
--color-tlf21-fieldedge: #2a2f3d;
--color-tlf21-fieldedge: oklch(0.3 0.018 270);
--color-tlf21-ink: #eceef6;
--color-tlf21-ink: oklch(0.95 0.006 270);
--color-tlf21-label: #c2c8d9;
--color-tlf21-label: oklch(0.84 0.014 270);
--color-tlf21-mut: #a0a7bb;
--color-tlf21-mut: oklch(0.72 0.018 270);
--color-tlf21-ph: #757d95;
--color-tlf21-ph: oklch(0.58 0.02 270);
--color-tlf21-acc: #b37cf0;
--color-tlf21-acc: oklch(0.72 0.18 300);
--color-tlf21-halo: #d4bcf6;
--color-tlf21-halo: oklch(0.72 0.18 300 / 0.22);
--color-tlf21-ring: #b9e88f;
--color-tlf21-ring: oklch(0.85 0.15 130 / 0.6);
--color-tlf21-btnink: #0a0b10;
--color-tlf21-btnink: oklch(0.15 0.01 270);
--font-tlf21-sans: 'Chivo', system-ui, sans-serif;
--animate-tlf21-spin: tlf-21-spin 5s linear infinite;
}
@keyframes tlf-21-spin {
to {
--tlf-21-spin: 360deg;
}
}
.tlf-21 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}
/* No conic-gradient / @property: a static linear ring instead of a flickering one. */
@supports not (background: conic-gradient(from 0deg, red, red)) {
.tlf-21 .tlf-21-ring::before {
background: linear-gradient(135deg, oklch(0.7 0.2 20), oklch(0.75 0.16 200));
}
.tlf-21 .tlf-21-ring::after {
display: none;
}
}<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=Chivo:wght@400;500;600;700;800&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=Chivo:wght@400;500;600;700;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The
@propertyregistration, the@keyframesand the@supportsfallback all stay in the stylesheet; the@themeanimation entry and eleven utilities do the rest. That split — register in CSS, compose in utilities — is the pattern for every custom-property animation in v4. - Repeating the conic gradient in both
before:andafter:is the port's redundancy cost; a CSS-only version could share it via a custom property, and doing that here (--ring: conic-gradient(...)in the@theme) is a reasonable refinement. -z-1/-z-2withisolatekeeps two negative-index pseudo-elements safely inside the wrapper's own stacking context.- UI change from the CSS original: Chivo replaces the system stack, muted text lifted from
#8b92a6tooklch(0.72), the button's lime/cyan gradient brightened to0.85so near-black button text clears 4.5:1, and the focus-visible ring on the CTA switched from the lime to a 60%-alpha version that reads against the animated ring behind it.
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 Animated Conic Gradient Border Login Card
Source: https://codefronts.com/components/tailwind-css-login-forms/tailwind-animated-conic-gradient-border-login-card/
A glowing conic light rotates continuously around the card's edge — the fast-moving-tech accent, rendered by animating one registered angle property rather than repainting a gradient. The @property registration is the part utilities cannot express, which makes this the clearest example in the collection of the CSS that must stay CSS.
## HTML
```html
<section class="tlf-21 w-full min-h-svh grid place-items-center bg-tlf21-stage font-tlf21-sans p-6">
<div class="tlf-21-ring relative isolate w-[min(380px,100%)] p-0.5 rounded-[20px] before:content-[''] before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:bg-[conic-gradient(from_var(--tlf-21-spin),oklch(0.7_0.2_20),oklch(0.72_0.18_300),oklch(0.75_0.16_200),oklch(0.78_0.18_130),oklch(0.7_0.2_20))] before:animate-tlf21-spin after:content-[''] after:absolute after:inset-0 after:-z-2 after:rounded-[inherit] after:opacity-70 after:blur-[16px] after:bg-[conic-gradient(from_var(--tlf-21-spin),oklch(0.7_0.2_20),oklch(0.72_0.18_300),oklch(0.75_0.16_200),oklch(0.78_0.18_130),oklch(0.7_0.2_20))] after:animate-tlf21-spin motion-reduce:before:animate-none motion-reduce:after:animate-none">
<form novalidate class="relative flex flex-col gap-3.5 p-8 rounded-[18px] bg-tlf21-card text-tlf21-ink">
<h1 class="text-2xl font-bold tracking-tight">Sign in</h1>
<p class="-mt-2 mb-1 text-[13.5px] text-tlf21-mut">Ship faster with Comet.</p>
<label class="flex flex-col gap-1.5"><span class="text-[13px] font-semibold text-tlf21-label">Email</span>
<input type="email" autocomplete="email" placeholder="you@company.com" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf21-ink bg-tlf21-field border-[1.5px] border-tlf21-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf21-ph focus-visible:outline-none focus-visible:border-tlf21-acc focus-visible:ring-4 focus-visible:ring-tlf21-halo"></label>
<label class="flex flex-col gap-1.5"><span class="text-[13px] font-semibold text-tlf21-label">Password</span>
<input type="password" autocomplete="current-password" placeholder="••••••••" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf21-ink bg-tlf21-field border-[1.5px] border-tlf21-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf21-ph focus-visible:outline-none focus-visible:border-tlf21-acc focus-visible:ring-4 focus-visible:ring-tlf21-halo"></label>
<button type="submit" class="mt-1 h-12 rounded-xl border-0 cursor-pointer text-tlf21-btnink text-[15px] font-bold bg-[linear-gradient(90deg,oklch(0.85_0.15_130),oklch(0.85_0.14_200))] transition-[filter,scale] duration-150 hover:brightness-105 active:scale-[0.985] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-tlf21-ring motion-reduce:transition-none motion-reduce:active:scale-100">Continue</button>
</form>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@property --tlf-21-spin {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-tlf21-stage: #07080d;
--color-tlf21-stage: oklch(0.12 0.01 270);
--color-tlf21-card: #12141d;
--color-tlf21-card: oklch(0.19 0.014 270);
--color-tlf21-field: #0e1019;
--color-tlf21-field: oklch(0.16 0.014 270);
--color-tlf21-fieldedge: #2a2f3d;
--color-tlf21-fieldedge: oklch(0.3 0.018 270);
--color-tlf21-ink: #eceef6;
--color-tlf21-ink: oklch(0.95 0.006 270);
--color-tlf21-label: #c2c8d9;
--color-tlf21-label: oklch(0.84 0.014 270);
--color-tlf21-mut: #a0a7bb;
--color-tlf21-mut: oklch(0.72 0.018 270);
--color-tlf21-ph: #757d95;
--color-tlf21-ph: oklch(0.58 0.02 270);
--color-tlf21-acc: #b37cf0;
--color-tlf21-acc: oklch(0.72 0.18 300);
--color-tlf21-halo: #d4bcf6;
--color-tlf21-halo: oklch(0.72 0.18 300 / 0.22);
--color-tlf21-ring: #b9e88f;
--color-tlf21-ring: oklch(0.85 0.15 130 / 0.6);
--color-tlf21-btnink: #0a0b10;
--color-tlf21-btnink: oklch(0.15 0.01 270);
--font-tlf21-sans: 'Chivo', system-ui, sans-serif;
--animate-tlf21-spin: tlf-21-spin 5s linear infinite;
}
@keyframes tlf-21-spin {
to {
--tlf-21-spin: 360deg;
}
}
.tlf-21 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}
/* No conic-gradient / @property: a static linear ring instead of a flickering one. */
@supports not (background: conic-gradient(from 0deg, red, red)) {
.tlf-21 .tlf-21-ring::before {
background: linear-gradient(135deg, oklch(0.7 0.2 20), oklch(0.75 0.16 200));
}
.tlf-21 .tlf-21-ring::after {
display: none;
}
}
```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 Animated Conic Gradient Border Login Card
Source: https://codefronts.com/components/tailwind-css-login-forms/tailwind-animated-conic-gradient-border-login-card/
A glowing conic light rotates continuously around the card's edge — the fast-moving-tech accent, rendered by animating one registered angle property rather than repainting a gradient. The @property registration is the part utilities cannot express, which makes this the clearest example in the collection of the CSS that must stay CSS.
## HTML
```html
<section class="tlf-21 w-full min-h-svh grid place-items-center bg-tlf21-stage font-tlf21-sans p-6">
<div class="tlf-21-ring relative isolate w-[min(380px,100%)] p-0.5 rounded-[20px] before:content-[''] before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:bg-[conic-gradient(from_var(--tlf-21-spin),oklch(0.7_0.2_20),oklch(0.72_0.18_300),oklch(0.75_0.16_200),oklch(0.78_0.18_130),oklch(0.7_0.2_20))] before:animate-tlf21-spin after:content-[''] after:absolute after:inset-0 after:-z-2 after:rounded-[inherit] after:opacity-70 after:blur-[16px] after:bg-[conic-gradient(from_var(--tlf-21-spin),oklch(0.7_0.2_20),oklch(0.72_0.18_300),oklch(0.75_0.16_200),oklch(0.78_0.18_130),oklch(0.7_0.2_20))] after:animate-tlf21-spin motion-reduce:before:animate-none motion-reduce:after:animate-none">
<form novalidate class="relative flex flex-col gap-3.5 p-8 rounded-[18px] bg-tlf21-card text-tlf21-ink">
<h1 class="text-2xl font-bold tracking-tight">Sign in</h1>
<p class="-mt-2 mb-1 text-[13.5px] text-tlf21-mut">Ship faster with Comet.</p>
<label class="flex flex-col gap-1.5"><span class="text-[13px] font-semibold text-tlf21-label">Email</span>
<input type="email" autocomplete="email" placeholder="you@company.com" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf21-ink bg-tlf21-field border-[1.5px] border-tlf21-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf21-ph focus-visible:outline-none focus-visible:border-tlf21-acc focus-visible:ring-4 focus-visible:ring-tlf21-halo"></label>
<label class="flex flex-col gap-1.5"><span class="text-[13px] font-semibold text-tlf21-label">Password</span>
<input type="password" autocomplete="current-password" placeholder="••••••••" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf21-ink bg-tlf21-field border-[1.5px] border-tlf21-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf21-ph focus-visible:outline-none focus-visible:border-tlf21-acc focus-visible:ring-4 focus-visible:ring-tlf21-halo"></label>
<button type="submit" class="mt-1 h-12 rounded-xl border-0 cursor-pointer text-tlf21-btnink text-[15px] font-bold bg-[linear-gradient(90deg,oklch(0.85_0.15_130),oklch(0.85_0.14_200))] transition-[filter,scale] duration-150 hover:brightness-105 active:scale-[0.985] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-tlf21-ring motion-reduce:transition-none motion-reduce:active:scale-100">Continue</button>
</form>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@property --tlf-21-spin {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-tlf21-stage: #07080d;
--color-tlf21-stage: oklch(0.12 0.01 270);
--color-tlf21-card: #12141d;
--color-tlf21-card: oklch(0.19 0.014 270);
--color-tlf21-field: #0e1019;
--color-tlf21-field: oklch(0.16 0.014 270);
--color-tlf21-fieldedge: #2a2f3d;
--color-tlf21-fieldedge: oklch(0.3 0.018 270);
--color-tlf21-ink: #eceef6;
--color-tlf21-ink: oklch(0.95 0.006 270);
--color-tlf21-label: #c2c8d9;
--color-tlf21-label: oklch(0.84 0.014 270);
--color-tlf21-mut: #a0a7bb;
--color-tlf21-mut: oklch(0.72 0.018 270);
--color-tlf21-ph: #757d95;
--color-tlf21-ph: oklch(0.58 0.02 270);
--color-tlf21-acc: #b37cf0;
--color-tlf21-acc: oklch(0.72 0.18 300);
--color-tlf21-halo: #d4bcf6;
--color-tlf21-halo: oklch(0.72 0.18 300 / 0.22);
--color-tlf21-ring: #b9e88f;
--color-tlf21-ring: oklch(0.85 0.15 130 / 0.6);
--color-tlf21-btnink: #0a0b10;
--color-tlf21-btnink: oklch(0.15 0.01 270);
--font-tlf21-sans: 'Chivo', system-ui, sans-serif;
--animate-tlf21-spin: tlf-21-spin 5s linear infinite;
}
@keyframes tlf-21-spin {
to {
--tlf-21-spin: 360deg;
}
}
.tlf-21 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}
/* No conic-gradient / @property: a static linear ring instead of a flickering one. */
@supports not (background: conic-gradient(from 0deg, red, red)) {
.tlf-21 .tlf-21-ring::before {
background: linear-gradient(135deg, oklch(0.7 0.2 20), oklch(0.75 0.16 200));
}
.tlf-21 .tlf-21-ring::after {
display: none;
}
}
```How this works
Animate the angle, not the gradient. An unregistered custom property is a string, so a keyframe from 0deg to 360deg would jump discretely. @property types it as an <angle>, the browser interpolates one number, and the pre-painted conic layer simply rotates — which is why this runs cool with two stacked copies.
Two pseudo-elements, one gradient. before: is the crisp ring, after: is the same gradient blurred to 16px at 70% opacity for the bloom. Both are utilities on the wrapper; nothing extra in the DOM.
The card is opaque. Form contrast never depends on where the light happens to be, which is the difference between a nice effect and a nice effect you can ship.
Make it yours
- Ring colours: the conic stops inside the
before:/after:utilities (keep them identical). - Speed: the duration in
--animate-tlf21-spin. - Glow:
after:blur-[16px]andafter:opacity-70. - Ring thickness: the wrapper's
p-0.5.
Gotchas — read before shipping
- Without
@propertythe angle cannot interpolate. The stylesheet's@supports not (background: conic-gradient(from 0deg, red, red))fallback paints a static linear border so it degrades rather than flickers. - Keep both pseudo-element gradients in sync — a mismatched glow reads as a rendering bug.
isolateon the wrapper is required: without its own stacking context,-z-1pseudo-elements can slide behind the page background and vanish.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
@property is Chrome 85, Safari 16.4, Firefox 128 — the narrowest floor in the collection. Older engines get the static gradient ring from the @supports fallback.