20 / 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 Multi-Step Login Wizard with Radio-Driven Track
A multi-stage sign-in that lowers upfront resistance: email, then password, then a security code, with inactive panels sliding off a translated track. Three radios hold the stage, Next/Back are their labels, and the port's interesting constraint is that Tailwind's peer only reaches later siblings — which is exactly how this markup is already arranged.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tlf-20 w-full min-h-svh grid place-items-center bg-tlf20-stage font-tlf20-sans p-6">
<form novalidate class="w-[min(380px,100%)] p-7 bg-white border border-tlf20-edge rounded-[18px] overflow-hidden shadow-[0_24px_56px_-38px_oklch(0.25_0.06_265/0.5)]">
<input type="radio" name="tlf-20-step" id="tlf-20-s1" checked class="peer/s1 sr-only" aria-label="Step 1 of 3: email">
<input type="radio" name="tlf-20-step" id="tlf-20-s2" class="peer/s2 sr-only" aria-label="Step 2 of 3: password">
<input type="radio" name="tlf-20-step" id="tlf-20-s3" class="peer/s3 sr-only" aria-label="Step 3 of 3: security code">
<div aria-hidden="true" class="h-1.5 mb-6 rounded-full bg-tlf20-track overflow-hidden">
<span class="block h-full w-1/3 rounded-full bg-tlf20-acc transition-[width] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] peer-checked/s2:w-2/3 peer-checked/s3:w-full motion-reduce:transition-none"></span>
</div>
<div class="overflow-hidden">
<div class="flex w-[300%] transition-transform duration-450 ease-[cubic-bezier(0.4,0,0.2,1)] peer-checked/s2:-translate-x-1/3 peer-checked/s3:-translate-x-2/3 motion-reduce:transition-none">
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Your email</legend>
<input type="email" autocomplete="email" placeholder="you@company.com" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<label for="tlf-20-s2" class="grid place-items-center h-12 rounded-xl cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter] duration-150 hover:brightness-110">Continue</label>
</fieldset>
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Your password</legend>
<input type="password" autocomplete="current-password" placeholder="••••••••" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<div class="flex gap-2.5">
<label for="tlf-20-s1" class="grid place-items-center basis-[92px] h-12 rounded-xl cursor-pointer bg-white border-[1.5px] border-tlf20-fieldedge text-tlf20-mut text-[15px] font-semibold transition-colors duration-150 hover:bg-tlf20-hover">Back</label>
<label for="tlf-20-s3" class="grid place-items-center flex-1 h-12 rounded-xl cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter] duration-150 hover:brightness-110">Continue</label>
</div>
</fieldset>
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Security code</legend>
<input type="text" inputmode="numeric" autocomplete="one-time-code" placeholder="6-digit code" required class="h-12 px-3.5 rounded-xl text-[15px] tracking-[0.2em] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:tracking-normal placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<div class="flex gap-2.5">
<label for="tlf-20-s2" class="grid place-items-center basis-[92px] h-12 rounded-xl cursor-pointer bg-white border-[1.5px] border-tlf20-fieldedge text-tlf20-mut text-[15px] font-semibold transition-colors duration-150 hover:bg-tlf20-hover">Back</label>
<button type="submit" class="flex-1 h-12 rounded-xl border-0 cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter,scale] duration-150 hover:brightness-110 active:scale-[0.985] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf20-ring motion-reduce:transition-none motion-reduce:active:scale-100">Sign in</button>
</div>
</fieldset>
</div>
</div>
</form>
</section><section class="tlf-20 w-full min-h-svh grid place-items-center bg-tlf20-stage font-tlf20-sans p-6">
<form novalidate class="w-[min(380px,100%)] p-7 bg-white border border-tlf20-edge rounded-[18px] overflow-hidden shadow-[0_24px_56px_-38px_oklch(0.25_0.06_265/0.5)]">
<input type="radio" name="tlf-20-step" id="tlf-20-s1" checked class="peer/s1 sr-only" aria-label="Step 1 of 3: email">
<input type="radio" name="tlf-20-step" id="tlf-20-s2" class="peer/s2 sr-only" aria-label="Step 2 of 3: password">
<input type="radio" name="tlf-20-step" id="tlf-20-s3" class="peer/s3 sr-only" aria-label="Step 3 of 3: security code">
<div aria-hidden="true" class="h-1.5 mb-6 rounded-full bg-tlf20-track overflow-hidden">
<span class="block h-full w-1/3 rounded-full bg-tlf20-acc transition-[width] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] peer-checked/s2:w-2/3 peer-checked/s3:w-full motion-reduce:transition-none"></span>
</div>
<div class="overflow-hidden">
<div class="flex w-[300%] transition-transform duration-450 ease-[cubic-bezier(0.4,0,0.2,1)] peer-checked/s2:-translate-x-1/3 peer-checked/s3:-translate-x-2/3 motion-reduce:transition-none">
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Your email</legend>
<input type="email" autocomplete="email" placeholder="you@company.com" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<label for="tlf-20-s2" class="grid place-items-center h-12 rounded-xl cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter] duration-150 hover:brightness-110">Continue</label>
</fieldset>
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Your password</legend>
<input type="password" autocomplete="current-password" placeholder="••••••••" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<div class="flex gap-2.5">
<label for="tlf-20-s1" class="grid place-items-center basis-[92px] h-12 rounded-xl cursor-pointer bg-white border-[1.5px] border-tlf20-fieldedge text-tlf20-mut text-[15px] font-semibold transition-colors duration-150 hover:bg-tlf20-hover">Back</label>
<label for="tlf-20-s3" class="grid place-items-center flex-1 h-12 rounded-xl cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter] duration-150 hover:brightness-110">Continue</label>
</div>
</fieldset>
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Security code</legend>
<input type="text" inputmode="numeric" autocomplete="one-time-code" placeholder="6-digit code" required class="h-12 px-3.5 rounded-xl text-[15px] tracking-[0.2em] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:tracking-normal placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<div class="flex gap-2.5">
<label for="tlf-20-s2" class="grid place-items-center basis-[92px] h-12 rounded-xl cursor-pointer bg-white border-[1.5px] border-tlf20-fieldedge text-tlf20-mut text-[15px] font-semibold transition-colors duration-150 hover:bg-tlf20-hover">Back</label>
<button type="submit" class="flex-1 h-12 rounded-xl border-0 cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter,scale] duration-150 hover:brightness-110 active:scale-[0.985] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf20-ring motion-reduce:transition-none motion-reduce:active:scale-100">Sign in</button>
</div>
</fieldset>
</div>
</div>
</form>
</section>@import "tailwindcss";
@theme {
--color-tlf20-stage: #eef1f7;
--color-tlf20-stage: oklch(0.95 0.008 265);
--color-tlf20-edge: #e5e9f2;
--color-tlf20-edge: oklch(0.92 0.008 265);
--color-tlf20-fieldedge: #d8deeb;
--color-tlf20-fieldedge: oklch(0.88 0.012 265);
--color-tlf20-field: #fafbfe;
--color-tlf20-field: oklch(0.99 0.003 265);
--color-tlf20-hover: #f2f5fb;
--color-tlf20-hover: oklch(0.97 0.006 265);
--color-tlf20-track: #e6eaf3;
--color-tlf20-track: oklch(0.93 0.008 265);
--color-tlf20-ink: #1a2237;
--color-tlf20-ink: oklch(0.25 0.04 265);
--color-tlf20-mut: #4e5a76;
--color-tlf20-mut: oklch(0.45 0.035 265);
--color-tlf20-ph: #949db5;
--color-tlf20-ph: oklch(0.67 0.025 265);
--color-tlf20-acc: #3e59c9;
--color-tlf20-acc: oklch(0.5 0.18 265);
--color-tlf20-halo: #bec8f0;
--color-tlf20-halo: oklch(0.5 0.18 265 / 0.16);
--color-tlf20-ring: #93a2ea;
--color-tlf20-ring: oklch(0.5 0.18 265 / 0.5);
--font-tlf20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tlf-20 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tlf20-stage: #eef1f7;
--color-tlf20-stage: oklch(0.95 0.008 265);
--color-tlf20-edge: #e5e9f2;
--color-tlf20-edge: oklch(0.92 0.008 265);
--color-tlf20-fieldedge: #d8deeb;
--color-tlf20-fieldedge: oklch(0.88 0.012 265);
--color-tlf20-field: #fafbfe;
--color-tlf20-field: oklch(0.99 0.003 265);
--color-tlf20-hover: #f2f5fb;
--color-tlf20-hover: oklch(0.97 0.006 265);
--color-tlf20-track: #e6eaf3;
--color-tlf20-track: oklch(0.93 0.008 265);
--color-tlf20-ink: #1a2237;
--color-tlf20-ink: oklch(0.25 0.04 265);
--color-tlf20-mut: #4e5a76;
--color-tlf20-mut: oklch(0.45 0.035 265);
--color-tlf20-ph: #949db5;
--color-tlf20-ph: oklch(0.67 0.025 265);
--color-tlf20-acc: #3e59c9;
--color-tlf20-acc: oklch(0.5 0.18 265);
--color-tlf20-halo: #bec8f0;
--color-tlf20-halo: oklch(0.5 0.18 265 / 0.16);
--color-tlf20-ring: #93a2ea;
--color-tlf20-ring: oklch(0.5 0.18 265 / 0.5);
--font-tlf20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tlf-20 {
width: 100%;
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-tlf20-stage: #eef1f7;
--color-tlf20-stage: oklch(0.95 0.008 265);
--color-tlf20-edge: #e5e9f2;
--color-tlf20-edge: oklch(0.92 0.008 265);
--color-tlf20-fieldedge: #d8deeb;
--color-tlf20-fieldedge: oklch(0.88 0.012 265);
--color-tlf20-field: #fafbfe;
--color-tlf20-field: oklch(0.99 0.003 265);
--color-tlf20-hover: #f2f5fb;
--color-tlf20-hover: oklch(0.97 0.006 265);
--color-tlf20-track: #e6eaf3;
--color-tlf20-track: oklch(0.93 0.008 265);
--color-tlf20-ink: #1a2237;
--color-tlf20-ink: oklch(0.25 0.04 265);
--color-tlf20-mut: #4e5a76;
--color-tlf20-mut: oklch(0.45 0.035 265);
--color-tlf20-ph: #949db5;
--color-tlf20-ph: oklch(0.67 0.025 265);
--color-tlf20-acc: #3e59c9;
--color-tlf20-acc: oklch(0.5 0.18 265);
--color-tlf20-halo: #bec8f0;
--color-tlf20-halo: oklch(0.5 0.18 265 / 0.16);
--color-tlf20-ring: #93a2ea;
--color-tlf20-ring: oklch(0.5 0.18 265 / 0.5);
--font-tlf20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tlf-20 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tlf20-stage: #eef1f7;
--color-tlf20-stage: oklch(0.95 0.008 265);
--color-tlf20-edge: #e5e9f2;
--color-tlf20-edge: oklch(0.92 0.008 265);
--color-tlf20-fieldedge: #d8deeb;
--color-tlf20-fieldedge: oklch(0.88 0.012 265);
--color-tlf20-field: #fafbfe;
--color-tlf20-field: oklch(0.99 0.003 265);
--color-tlf20-hover: #f2f5fb;
--color-tlf20-hover: oklch(0.97 0.006 265);
--color-tlf20-track: #e6eaf3;
--color-tlf20-track: oklch(0.93 0.008 265);
--color-tlf20-ink: #1a2237;
--color-tlf20-ink: oklch(0.25 0.04 265);
--color-tlf20-mut: #4e5a76;
--color-tlf20-mut: oklch(0.45 0.035 265);
--color-tlf20-ph: #949db5;
--color-tlf20-ph: oklch(0.67 0.025 265);
--color-tlf20-acc: #3e59c9;
--color-tlf20-acc: oklch(0.5 0.18 265);
--color-tlf20-halo: #bec8f0;
--color-tlf20-halo: oklch(0.5 0.18 265 / 0.16);
--color-tlf20-ring: #93a2ea;
--color-tlf20-ring: oklch(0.5 0.18 265 / 0.5);
--font-tlf20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tlf-20 {
width: 100%;
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=Plus+Jakarta+Sans: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=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Named peers (
peer/s1,peer/s2,peer/s3) are what make this port readable: the progress fill's class list literally says "one third, two thirds at step 2, full at step 3", where the CSS version needed:nth-of-type()arithmetic to say the same thing. -translate-x-1/3and-translate-x-2/3are exact fractions rather than the original's33.333%, which removes a rounding seam that was visible at certain widths.- The
inertcaveat is real and worth stating rather than hiding: CSS-only wizards leave off-screen fields in the tab order, and that is the pattern's cost. - UI change from the CSS original: Plus Jakarta Sans replaces the system stack, the radios gained
aria-labels that announce "Step 2 of 3", Back buttons went to 48px with a real hover, the muted Back ink darkened from#54607ctooklch(0.45 0.035 265), and the OTP field tracks its digits at0.2em.
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 Multi-Step Login Wizard with Radio-Driven Track
Source: https://codefronts.com/components/tailwind-css-login-forms/tailwind-multi-step-login-wizard-radio-track/
A multi-stage sign-in that lowers upfront resistance: email, then password, then a security code, with inactive panels sliding off a translated track. Three radios hold the stage, Next/Back are their labels, and the port's interesting constraint is that Tailwind's peer only reaches later siblings — which is exactly how this markup is already arranged.
## HTML
```html
<section class="tlf-20 w-full min-h-svh grid place-items-center bg-tlf20-stage font-tlf20-sans p-6">
<form novalidate class="w-[min(380px,100%)] p-7 bg-white border border-tlf20-edge rounded-[18px] overflow-hidden shadow-[0_24px_56px_-38px_oklch(0.25_0.06_265/0.5)]">
<input type="radio" name="tlf-20-step" id="tlf-20-s1" checked class="peer/s1 sr-only" aria-label="Step 1 of 3: email">
<input type="radio" name="tlf-20-step" id="tlf-20-s2" class="peer/s2 sr-only" aria-label="Step 2 of 3: password">
<input type="radio" name="tlf-20-step" id="tlf-20-s3" class="peer/s3 sr-only" aria-label="Step 3 of 3: security code">
<div aria-hidden="true" class="h-1.5 mb-6 rounded-full bg-tlf20-track overflow-hidden">
<span class="block h-full w-1/3 rounded-full bg-tlf20-acc transition-[width] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] peer-checked/s2:w-2/3 peer-checked/s3:w-full motion-reduce:transition-none"></span>
</div>
<div class="overflow-hidden">
<div class="flex w-[300%] transition-transform duration-450 ease-[cubic-bezier(0.4,0,0.2,1)] peer-checked/s2:-translate-x-1/3 peer-checked/s3:-translate-x-2/3 motion-reduce:transition-none">
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Your email</legend>
<input type="email" autocomplete="email" placeholder="you@company.com" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<label for="tlf-20-s2" class="grid place-items-center h-12 rounded-xl cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter] duration-150 hover:brightness-110">Continue</label>
</fieldset>
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Your password</legend>
<input type="password" autocomplete="current-password" placeholder="••••••••" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<div class="flex gap-2.5">
<label for="tlf-20-s1" class="grid place-items-center basis-[92px] h-12 rounded-xl cursor-pointer bg-white border-[1.5px] border-tlf20-fieldedge text-tlf20-mut text-[15px] font-semibold transition-colors duration-150 hover:bg-tlf20-hover">Back</label>
<label for="tlf-20-s3" class="grid place-items-center flex-1 h-12 rounded-xl cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter] duration-150 hover:brightness-110">Continue</label>
</div>
</fieldset>
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Security code</legend>
<input type="text" inputmode="numeric" autocomplete="one-time-code" placeholder="6-digit code" required class="h-12 px-3.5 rounded-xl text-[15px] tracking-[0.2em] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:tracking-normal placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<div class="flex gap-2.5">
<label for="tlf-20-s2" class="grid place-items-center basis-[92px] h-12 rounded-xl cursor-pointer bg-white border-[1.5px] border-tlf20-fieldedge text-tlf20-mut text-[15px] font-semibold transition-colors duration-150 hover:bg-tlf20-hover">Back</label>
<button type="submit" class="flex-1 h-12 rounded-xl border-0 cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter,scale] duration-150 hover:brightness-110 active:scale-[0.985] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf20-ring motion-reduce:transition-none motion-reduce:active:scale-100">Sign in</button>
</div>
</fieldset>
</div>
</div>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tlf20-stage: #eef1f7;
--color-tlf20-stage: oklch(0.95 0.008 265);
--color-tlf20-edge: #e5e9f2;
--color-tlf20-edge: oklch(0.92 0.008 265);
--color-tlf20-fieldedge: #d8deeb;
--color-tlf20-fieldedge: oklch(0.88 0.012 265);
--color-tlf20-field: #fafbfe;
--color-tlf20-field: oklch(0.99 0.003 265);
--color-tlf20-hover: #f2f5fb;
--color-tlf20-hover: oklch(0.97 0.006 265);
--color-tlf20-track: #e6eaf3;
--color-tlf20-track: oklch(0.93 0.008 265);
--color-tlf20-ink: #1a2237;
--color-tlf20-ink: oklch(0.25 0.04 265);
--color-tlf20-mut: #4e5a76;
--color-tlf20-mut: oklch(0.45 0.035 265);
--color-tlf20-ph: #949db5;
--color-tlf20-ph: oklch(0.67 0.025 265);
--color-tlf20-acc: #3e59c9;
--color-tlf20-acc: oklch(0.5 0.18 265);
--color-tlf20-halo: #bec8f0;
--color-tlf20-halo: oklch(0.5 0.18 265 / 0.16);
--color-tlf20-ring: #93a2ea;
--color-tlf20-ring: oklch(0.5 0.18 265 / 0.5);
--font-tlf20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tlf-20 {
width: 100%;
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 Multi-Step Login Wizard with Radio-Driven Track
Source: https://codefronts.com/components/tailwind-css-login-forms/tailwind-multi-step-login-wizard-radio-track/
A multi-stage sign-in that lowers upfront resistance: email, then password, then a security code, with inactive panels sliding off a translated track. Three radios hold the stage, Next/Back are their labels, and the port's interesting constraint is that Tailwind's peer only reaches later siblings — which is exactly how this markup is already arranged.
## HTML
```html
<section class="tlf-20 w-full min-h-svh grid place-items-center bg-tlf20-stage font-tlf20-sans p-6">
<form novalidate class="w-[min(380px,100%)] p-7 bg-white border border-tlf20-edge rounded-[18px] overflow-hidden shadow-[0_24px_56px_-38px_oklch(0.25_0.06_265/0.5)]">
<input type="radio" name="tlf-20-step" id="tlf-20-s1" checked class="peer/s1 sr-only" aria-label="Step 1 of 3: email">
<input type="radio" name="tlf-20-step" id="tlf-20-s2" class="peer/s2 sr-only" aria-label="Step 2 of 3: password">
<input type="radio" name="tlf-20-step" id="tlf-20-s3" class="peer/s3 sr-only" aria-label="Step 3 of 3: security code">
<div aria-hidden="true" class="h-1.5 mb-6 rounded-full bg-tlf20-track overflow-hidden">
<span class="block h-full w-1/3 rounded-full bg-tlf20-acc transition-[width] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] peer-checked/s2:w-2/3 peer-checked/s3:w-full motion-reduce:transition-none"></span>
</div>
<div class="overflow-hidden">
<div class="flex w-[300%] transition-transform duration-450 ease-[cubic-bezier(0.4,0,0.2,1)] peer-checked/s2:-translate-x-1/3 peer-checked/s3:-translate-x-2/3 motion-reduce:transition-none">
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Your email</legend>
<input type="email" autocomplete="email" placeholder="you@company.com" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<label for="tlf-20-s2" class="grid place-items-center h-12 rounded-xl cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter] duration-150 hover:brightness-110">Continue</label>
</fieldset>
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Your password</legend>
<input type="password" autocomplete="current-password" placeholder="••••••••" required class="h-12 px-3.5 rounded-xl text-[15px] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<div class="flex gap-2.5">
<label for="tlf-20-s1" class="grid place-items-center basis-[92px] h-12 rounded-xl cursor-pointer bg-white border-[1.5px] border-tlf20-fieldedge text-tlf20-mut text-[15px] font-semibold transition-colors duration-150 hover:bg-tlf20-hover">Back</label>
<label for="tlf-20-s3" class="grid place-items-center flex-1 h-12 rounded-xl cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter] duration-150 hover:brightness-110">Continue</label>
</div>
</fieldset>
<fieldset class="w-1/3 px-0.5 border-0 flex flex-col gap-3.5">
<legend class="mb-1 p-0 text-[19px] font-bold text-tlf20-ink">Security code</legend>
<input type="text" inputmode="numeric" autocomplete="one-time-code" placeholder="6-digit code" required class="h-12 px-3.5 rounded-xl text-[15px] tracking-[0.2em] text-tlf20-ink bg-tlf20-field border-[1.5px] border-tlf20-fieldedge transition-[border-color,box-shadow] duration-150 placeholder:tracking-normal placeholder:text-tlf20-ph focus-visible:outline-none focus-visible:border-tlf20-acc focus-visible:ring-4 focus-visible:ring-tlf20-halo">
<div class="flex gap-2.5">
<label for="tlf-20-s2" class="grid place-items-center basis-[92px] h-12 rounded-xl cursor-pointer bg-white border-[1.5px] border-tlf20-fieldedge text-tlf20-mut text-[15px] font-semibold transition-colors duration-150 hover:bg-tlf20-hover">Back</label>
<button type="submit" class="flex-1 h-12 rounded-xl border-0 cursor-pointer bg-tlf20-acc text-white text-[15px] font-semibold transition-[filter,scale] duration-150 hover:brightness-110 active:scale-[0.985] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf20-ring motion-reduce:transition-none motion-reduce:active:scale-100">Sign in</button>
</div>
</fieldset>
</div>
</div>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tlf20-stage: #eef1f7;
--color-tlf20-stage: oklch(0.95 0.008 265);
--color-tlf20-edge: #e5e9f2;
--color-tlf20-edge: oklch(0.92 0.008 265);
--color-tlf20-fieldedge: #d8deeb;
--color-tlf20-fieldedge: oklch(0.88 0.012 265);
--color-tlf20-field: #fafbfe;
--color-tlf20-field: oklch(0.99 0.003 265);
--color-tlf20-hover: #f2f5fb;
--color-tlf20-hover: oklch(0.97 0.006 265);
--color-tlf20-track: #e6eaf3;
--color-tlf20-track: oklch(0.93 0.008 265);
--color-tlf20-ink: #1a2237;
--color-tlf20-ink: oklch(0.25 0.04 265);
--color-tlf20-mut: #4e5a76;
--color-tlf20-mut: oklch(0.45 0.035 265);
--color-tlf20-ph: #949db5;
--color-tlf20-ph: oklch(0.67 0.025 265);
--color-tlf20-acc: #3e59c9;
--color-tlf20-acc: oklch(0.5 0.18 265);
--color-tlf20-halo: #bec8f0;
--color-tlf20-halo: oklch(0.5 0.18 265 / 0.16);
--color-tlf20-ring: #93a2ea;
--color-tlf20-ring: oklch(0.5 0.18 265 / 0.5);
--font-tlf20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tlf-20 {
width: 100%;
min-height: 100svh;
}
```How this works
Radios, not checkboxes. Exactly one stage can be active, which is what a radio group means. Three sr-only radios sit first in the form; the progress bar and the track are later siblings, so every peer-checked/sN: variant resolves.
The track translates, nothing hides. w-[300%] with three w-1/3 panels, shifted by thirds. No display:none anywhere, so tab order is preserved and the browser never has to re-layout the card between steps.
Next and Back are labels. Each one points at the radio for its stage, so the whole wizard runs on keyboard with no script — and because the radios are real form controls, arrow keys move between stages too.
Make it yours
- Add a stage: a fourth radio,
w-[400%],w-1/4panels and quarter translates. - Slide feel:
duration-450and the shared cubic-bezier. - Accent:
--color-tlf20-accdrives progress, buttons and rings. - Replace the step content with your real fields — the track does not care what is inside.
Gotchas — read before shipping
- Panels must be
overflow-hidden's children, nothidden. Toggling display kills the animation and moves focus unpredictably. - Off-screen steps remain focusable — a production wizard should add
inertto inactive panels, which CSS cannot do. peer-checked/s2:requires the named peer (peer/s2) on the radio; unnamed peers all resolve to the nearest preceding.peer, which silently makes steps 2 and 3 identical.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Radio :checked sibling state and transform transitions are universally supported; the floor is Tailwind v4's generated sheet.