03 / 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 Floating Label Login Form with peer Variants
The Material-modern favourite: the label sits inside the field, then glides up to a miniaturised top label the moment the field is focused or filled — no JavaScript, no keystroke listeners. This is peer's showcase demo: the CSS original's input:focus + label, input:not(:placeholder-shown) + label becomes peer-focus: and peer-[:not(:placeholder-shown)]: on the label itself.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tlf-03 w-full min-h-svh grid place-items-center bg-tlf03-stage font-tlf03-sans p-6">
<form novalidate class="w-[min(380px,100%)] flex flex-col gap-6 p-8 bg-white border border-tlf03-edge rounded-2xl shadow-[0_22px_50px_-36px_oklch(0.3_0.04_220/0.5)]">
<h1 class="text-2xl font-bold tracking-tight text-tlf03-ink">Log in</h1>
<div class="relative focus-within:[&>input]:border-tlf03-acc">
<input id="tlf-03-email" type="email" autocomplete="email" placeholder=" " required class="peer w-full h-14 pt-[18px] pb-1 px-0.5 text-base text-tlf03-ink bg-transparent border-0 border-b-2 border-tlf03-line transition-colors duration-200 focus-visible:outline-none">
<label for="tlf-03-email" class="absolute left-0.5 top-[18px] text-base text-tlf03-ph pointer-events-none origin-top-left transition-[translate,scale,color] duration-200 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-tlf03-acc peer-[:not(:placeholder-shown)]:-translate-y-4 peer-[:not(:placeholder-shown)]:scale-75 peer-[:not(:placeholder-shown)]:text-tlf03-acc motion-reduce:transition-none">Email address</label>
</div>
<div class="relative focus-within:[&>input]:border-tlf03-acc">
<input id="tlf-03-pw" type="password" autocomplete="current-password" placeholder=" " required class="peer w-full h-14 pt-[18px] pb-1 px-0.5 text-base text-tlf03-ink bg-transparent border-0 border-b-2 border-tlf03-line transition-colors duration-200 focus-visible:outline-none">
<label for="tlf-03-pw" class="absolute left-0.5 top-[18px] text-base text-tlf03-ph pointer-events-none origin-top-left transition-[translate,scale,color] duration-200 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-tlf03-acc peer-[:not(:placeholder-shown)]:-translate-y-4 peer-[:not(:placeholder-shown)]:scale-75 peer-[:not(:placeholder-shown)]:text-tlf03-acc motion-reduce:transition-none">Password</label>
</div>
<div class="flex items-center justify-between text-[13px]">
<label class="flex items-center gap-2 min-h-11 text-tlf03-mut cursor-pointer"><input type="checkbox" class="size-4 accent-tlf03-acc focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tlf03-acc"> Remember me</label>
<a href="#" class="font-semibold text-tlf03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tlf03-acc">Forgot?</a>
</div>
<button type="submit" class="h-13 rounded-xl border-0 cursor-pointer bg-tlf03-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-3 focus-visible:outline-tlf03-ring motion-reduce:transition-none motion-reduce:active:scale-100">Continue</button>
</form>
</section><section class="tlf-03 w-full min-h-svh grid place-items-center bg-tlf03-stage font-tlf03-sans p-6">
<form novalidate class="w-[min(380px,100%)] flex flex-col gap-6 p-8 bg-white border border-tlf03-edge rounded-2xl shadow-[0_22px_50px_-36px_oklch(0.3_0.04_220/0.5)]">
<h1 class="text-2xl font-bold tracking-tight text-tlf03-ink">Log in</h1>
<div class="relative focus-within:[&>input]:border-tlf03-acc">
<input id="tlf-03-email" type="email" autocomplete="email" placeholder=" " required class="peer w-full h-14 pt-[18px] pb-1 px-0.5 text-base text-tlf03-ink bg-transparent border-0 border-b-2 border-tlf03-line transition-colors duration-200 focus-visible:outline-none">
<label for="tlf-03-email" class="absolute left-0.5 top-[18px] text-base text-tlf03-ph pointer-events-none origin-top-left transition-[translate,scale,color] duration-200 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-tlf03-acc peer-[:not(:placeholder-shown)]:-translate-y-4 peer-[:not(:placeholder-shown)]:scale-75 peer-[:not(:placeholder-shown)]:text-tlf03-acc motion-reduce:transition-none">Email address</label>
</div>
<div class="relative focus-within:[&>input]:border-tlf03-acc">
<input id="tlf-03-pw" type="password" autocomplete="current-password" placeholder=" " required class="peer w-full h-14 pt-[18px] pb-1 px-0.5 text-base text-tlf03-ink bg-transparent border-0 border-b-2 border-tlf03-line transition-colors duration-200 focus-visible:outline-none">
<label for="tlf-03-pw" class="absolute left-0.5 top-[18px] text-base text-tlf03-ph pointer-events-none origin-top-left transition-[translate,scale,color] duration-200 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-tlf03-acc peer-[:not(:placeholder-shown)]:-translate-y-4 peer-[:not(:placeholder-shown)]:scale-75 peer-[:not(:placeholder-shown)]:text-tlf03-acc motion-reduce:transition-none">Password</label>
</div>
<div class="flex items-center justify-between text-[13px]">
<label class="flex items-center gap-2 min-h-11 text-tlf03-mut cursor-pointer"><input type="checkbox" class="size-4 accent-tlf03-acc focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tlf03-acc"> Remember me</label>
<a href="#" class="font-semibold text-tlf03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tlf03-acc">Forgot?</a>
</div>
<button type="submit" class="h-13 rounded-xl border-0 cursor-pointer bg-tlf03-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-3 focus-visible:outline-tlf03-ring motion-reduce:transition-none motion-reduce:active:scale-100">Continue</button>
</form>
</section>@import "tailwindcss";
@theme {
--color-tlf03-stage: #f3f6f8;
--color-tlf03-stage: oklch(0.96 0.006 220);
--color-tlf03-edge: #e3e8ee;
--color-tlf03-edge: oklch(0.92 0.008 220);
--color-tlf03-line: #cdd6dd;
--color-tlf03-line: oklch(0.86 0.012 220);
--color-tlf03-ink: #12222b;
--color-tlf03-ink: oklch(0.24 0.03 220);
--color-tlf03-mut: #51606a;
--color-tlf03-mut: oklch(0.48 0.02 220);
--color-tlf03-ph: #76858f;
--color-tlf03-ph: oklch(0.6 0.02 220);
--color-tlf03-acc: #1a8aa8;
--color-tlf03-acc: oklch(0.55 0.11 220);
--color-tlf03-ring: #7fc3d6;
--color-tlf03-ring: oklch(0.55 0.11 220 / 0.5);
--font-tlf03-sans: 'DM Sans', system-ui, sans-serif;
}
.tlf-03 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tlf03-stage: #f3f6f8;
--color-tlf03-stage: oklch(0.96 0.006 220);
--color-tlf03-edge: #e3e8ee;
--color-tlf03-edge: oklch(0.92 0.008 220);
--color-tlf03-line: #cdd6dd;
--color-tlf03-line: oklch(0.86 0.012 220);
--color-tlf03-ink: #12222b;
--color-tlf03-ink: oklch(0.24 0.03 220);
--color-tlf03-mut: #51606a;
--color-tlf03-mut: oklch(0.48 0.02 220);
--color-tlf03-ph: #76858f;
--color-tlf03-ph: oklch(0.6 0.02 220);
--color-tlf03-acc: #1a8aa8;
--color-tlf03-acc: oklch(0.55 0.11 220);
--color-tlf03-ring: #7fc3d6;
--color-tlf03-ring: oklch(0.55 0.11 220 / 0.5);
--font-tlf03-sans: 'DM Sans', system-ui, sans-serif;
}
.tlf-03 {
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-tlf03-stage: #f3f6f8;
--color-tlf03-stage: oklch(0.96 0.006 220);
--color-tlf03-edge: #e3e8ee;
--color-tlf03-edge: oklch(0.92 0.008 220);
--color-tlf03-line: #cdd6dd;
--color-tlf03-line: oklch(0.86 0.012 220);
--color-tlf03-ink: #12222b;
--color-tlf03-ink: oklch(0.24 0.03 220);
--color-tlf03-mut: #51606a;
--color-tlf03-mut: oklch(0.48 0.02 220);
--color-tlf03-ph: #76858f;
--color-tlf03-ph: oklch(0.6 0.02 220);
--color-tlf03-acc: #1a8aa8;
--color-tlf03-acc: oklch(0.55 0.11 220);
--color-tlf03-ring: #7fc3d6;
--color-tlf03-ring: oklch(0.55 0.11 220 / 0.5);
--font-tlf03-sans: 'DM Sans', system-ui, sans-serif;
}
.tlf-03 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tlf03-stage: #f3f6f8;
--color-tlf03-stage: oklch(0.96 0.006 220);
--color-tlf03-edge: #e3e8ee;
--color-tlf03-edge: oklch(0.92 0.008 220);
--color-tlf03-line: #cdd6dd;
--color-tlf03-line: oklch(0.86 0.012 220);
--color-tlf03-ink: #12222b;
--color-tlf03-ink: oklch(0.24 0.03 220);
--color-tlf03-mut: #51606a;
--color-tlf03-mut: oklch(0.48 0.02 220);
--color-tlf03-ph: #76858f;
--color-tlf03-ph: oklch(0.6 0.02 220);
--color-tlf03-acc: #1a8aa8;
--color-tlf03-acc: oklch(0.55 0.11 220);
--color-tlf03-ring: #7fc3d6;
--color-tlf03-ring: oklch(0.55 0.11 220 / 0.5);
--font-tlf03-sans: 'DM Sans', system-ui, sans-serif;
}
.tlf-03 {
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=DM+Sans: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=DM+Sans:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
input:not(:placeholder-shown) + labelhas no shorthand variant, so it becomespeer-[:not(:placeholder-shown)]:— the arbitrary peer form, and the single most useful thing to know about porting floating labels.focus-within:[&>input]:border-tlf03-accon the wrapper is the one place an arbitrary child selector earns its keep: the alternative is repeating the focus colour on both the input's ownfocus-visible:and a wrapper rule.transition-[translate,scale,color]rather thantransition-transform, because v4 splits transforms into separatetranslate/scaleproperties — a v3 habit that silently stops animating here.- UI change from the CSS original: DM Sans replaces
'Segoe UI', the resting label colour darkened from#8695a0tooklch(0.6 0.02 220)(4.5:1 on white), the accent moved to a slightly deeper teal for the same reason, and the Remember-me row gained a 44px target.
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 Floating Label Login Form with peer Variants
Source: https://codefronts.com/components/tailwind-css-login-forms/tailwind-floating-label-login-form-peer-variants/
The Material-modern favourite: the label sits inside the field, then glides up to a miniaturised top label the moment the field is focused or filled — no JavaScript, no keystroke listeners. This is peer's showcase demo: the CSS original's input:focus + label, input:not(:placeholder-shown) + label becomes peer-focus: and peer-[:not(:placeholder-shown)]: on the label itself.
## HTML
```html
<section class="tlf-03 w-full min-h-svh grid place-items-center bg-tlf03-stage font-tlf03-sans p-6">
<form novalidate class="w-[min(380px,100%)] flex flex-col gap-6 p-8 bg-white border border-tlf03-edge rounded-2xl shadow-[0_22px_50px_-36px_oklch(0.3_0.04_220/0.5)]">
<h1 class="text-2xl font-bold tracking-tight text-tlf03-ink">Log in</h1>
<div class="relative focus-within:[&>input]:border-tlf03-acc">
<input id="tlf-03-email" type="email" autocomplete="email" placeholder=" " required class="peer w-full h-14 pt-[18px] pb-1 px-0.5 text-base text-tlf03-ink bg-transparent border-0 border-b-2 border-tlf03-line transition-colors duration-200 focus-visible:outline-none">
<label for="tlf-03-email" class="absolute left-0.5 top-[18px] text-base text-tlf03-ph pointer-events-none origin-top-left transition-[translate,scale,color] duration-200 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-tlf03-acc peer-[:not(:placeholder-shown)]:-translate-y-4 peer-[:not(:placeholder-shown)]:scale-75 peer-[:not(:placeholder-shown)]:text-tlf03-acc motion-reduce:transition-none">Email address</label>
</div>
<div class="relative focus-within:[&>input]:border-tlf03-acc">
<input id="tlf-03-pw" type="password" autocomplete="current-password" placeholder=" " required class="peer w-full h-14 pt-[18px] pb-1 px-0.5 text-base text-tlf03-ink bg-transparent border-0 border-b-2 border-tlf03-line transition-colors duration-200 focus-visible:outline-none">
<label for="tlf-03-pw" class="absolute left-0.5 top-[18px] text-base text-tlf03-ph pointer-events-none origin-top-left transition-[translate,scale,color] duration-200 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-tlf03-acc peer-[:not(:placeholder-shown)]:-translate-y-4 peer-[:not(:placeholder-shown)]:scale-75 peer-[:not(:placeholder-shown)]:text-tlf03-acc motion-reduce:transition-none">Password</label>
</div>
<div class="flex items-center justify-between text-[13px]">
<label class="flex items-center gap-2 min-h-11 text-tlf03-mut cursor-pointer"><input type="checkbox" class="size-4 accent-tlf03-acc focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tlf03-acc"> Remember me</label>
<a href="#" class="font-semibold text-tlf03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tlf03-acc">Forgot?</a>
</div>
<button type="submit" class="h-13 rounded-xl border-0 cursor-pointer bg-tlf03-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-3 focus-visible:outline-tlf03-ring motion-reduce:transition-none motion-reduce:active:scale-100">Continue</button>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tlf03-stage: #f3f6f8;
--color-tlf03-stage: oklch(0.96 0.006 220);
--color-tlf03-edge: #e3e8ee;
--color-tlf03-edge: oklch(0.92 0.008 220);
--color-tlf03-line: #cdd6dd;
--color-tlf03-line: oklch(0.86 0.012 220);
--color-tlf03-ink: #12222b;
--color-tlf03-ink: oklch(0.24 0.03 220);
--color-tlf03-mut: #51606a;
--color-tlf03-mut: oklch(0.48 0.02 220);
--color-tlf03-ph: #76858f;
--color-tlf03-ph: oklch(0.6 0.02 220);
--color-tlf03-acc: #1a8aa8;
--color-tlf03-acc: oklch(0.55 0.11 220);
--color-tlf03-ring: #7fc3d6;
--color-tlf03-ring: oklch(0.55 0.11 220 / 0.5);
--font-tlf03-sans: 'DM Sans', system-ui, sans-serif;
}
.tlf-03 {
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 Floating Label Login Form with peer Variants
Source: https://codefronts.com/components/tailwind-css-login-forms/tailwind-floating-label-login-form-peer-variants/
The Material-modern favourite: the label sits inside the field, then glides up to a miniaturised top label the moment the field is focused or filled — no JavaScript, no keystroke listeners. This is peer's showcase demo: the CSS original's input:focus + label, input:not(:placeholder-shown) + label becomes peer-focus: and peer-[:not(:placeholder-shown)]: on the label itself.
## HTML
```html
<section class="tlf-03 w-full min-h-svh grid place-items-center bg-tlf03-stage font-tlf03-sans p-6">
<form novalidate class="w-[min(380px,100%)] flex flex-col gap-6 p-8 bg-white border border-tlf03-edge rounded-2xl shadow-[0_22px_50px_-36px_oklch(0.3_0.04_220/0.5)]">
<h1 class="text-2xl font-bold tracking-tight text-tlf03-ink">Log in</h1>
<div class="relative focus-within:[&>input]:border-tlf03-acc">
<input id="tlf-03-email" type="email" autocomplete="email" placeholder=" " required class="peer w-full h-14 pt-[18px] pb-1 px-0.5 text-base text-tlf03-ink bg-transparent border-0 border-b-2 border-tlf03-line transition-colors duration-200 focus-visible:outline-none">
<label for="tlf-03-email" class="absolute left-0.5 top-[18px] text-base text-tlf03-ph pointer-events-none origin-top-left transition-[translate,scale,color] duration-200 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-tlf03-acc peer-[:not(:placeholder-shown)]:-translate-y-4 peer-[:not(:placeholder-shown)]:scale-75 peer-[:not(:placeholder-shown)]:text-tlf03-acc motion-reduce:transition-none">Email address</label>
</div>
<div class="relative focus-within:[&>input]:border-tlf03-acc">
<input id="tlf-03-pw" type="password" autocomplete="current-password" placeholder=" " required class="peer w-full h-14 pt-[18px] pb-1 px-0.5 text-base text-tlf03-ink bg-transparent border-0 border-b-2 border-tlf03-line transition-colors duration-200 focus-visible:outline-none">
<label for="tlf-03-pw" class="absolute left-0.5 top-[18px] text-base text-tlf03-ph pointer-events-none origin-top-left transition-[translate,scale,color] duration-200 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-tlf03-acc peer-[:not(:placeholder-shown)]:-translate-y-4 peer-[:not(:placeholder-shown)]:scale-75 peer-[:not(:placeholder-shown)]:text-tlf03-acc motion-reduce:transition-none">Password</label>
</div>
<div class="flex items-center justify-between text-[13px]">
<label class="flex items-center gap-2 min-h-11 text-tlf03-mut cursor-pointer"><input type="checkbox" class="size-4 accent-tlf03-acc focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tlf03-acc"> Remember me</label>
<a href="#" class="font-semibold text-tlf03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tlf03-acc">Forgot?</a>
</div>
<button type="submit" class="h-13 rounded-xl border-0 cursor-pointer bg-tlf03-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-3 focus-visible:outline-tlf03-ring motion-reduce:transition-none motion-reduce:active:scale-100">Continue</button>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tlf03-stage: #f3f6f8;
--color-tlf03-stage: oklch(0.96 0.006 220);
--color-tlf03-edge: #e3e8ee;
--color-tlf03-edge: oklch(0.92 0.008 220);
--color-tlf03-line: #cdd6dd;
--color-tlf03-line: oklch(0.86 0.012 220);
--color-tlf03-ink: #12222b;
--color-tlf03-ink: oklch(0.24 0.03 220);
--color-tlf03-mut: #51606a;
--color-tlf03-mut: oklch(0.48 0.02 220);
--color-tlf03-ph: #76858f;
--color-tlf03-ph: oklch(0.6 0.02 220);
--color-tlf03-acc: #1a8aa8;
--color-tlf03-acc: oklch(0.55 0.11 220);
--color-tlf03-ring: #7fc3d6;
--color-tlf03-ring: oklch(0.55 0.11 220 / 0.5);
--font-tlf03-sans: 'DM Sans', system-ui, sans-serif;
}
.tlf-03 {
width: 100%;
min-height: 100svh;
}
```How this works
peer is the sibling combinator. Mark the input peer, and any following sibling can read its state: peer-focus:, peer-disabled:, and for anything without a shorthand, the arbitrary form peer-[:not(:placeholder-shown)]:. That ordering constraint is the one rule to remember — the label must come after the input in the DOM, which it already did.
The float is still a transform. -translate-y-4 scale-75 from origin-top-left, never top and font-size, so the label rides the compositor and the field's height never changes. motion-reduce:transition-none makes it an instant swap rather than a slow one.
The placeholder is a single space, still. placeholder=" " is what makes :placeholder-shown mean "empty"; the port cannot remove that requirement, and the <label for> stays a real label so tapping it focuses the field.
Make it yours
- Float distance and size:
-translate-y-4andscale-75, one number each. - Boxed instead of underlined: swap
border-b-2forborder rounded-xland addpx-3.5to both input and label offsets. - Accent:
--color-tlf03-accdrives underline, floated label, checkbox and button. - Field height is
h-14; the label's restingtop-[18px]moves with it.
Gotchas — read before shipping
placeholder=" "with a literal space is mandatory. An absent or empty placeholder makes:placeholder-shownmatch forever and the label never floats.peeronly reaches later siblings. Put the label before the input and the class list compiles to CSS that can never match — silently.- Don't replace the label with a placeholder. A floated label is the accessible name; a placeholder disappears the moment typing starts, which is precisely when users need it.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:placeholder-shown and :focus-within are Baseline since 2017/2018; the floor is Tailwind v4's generated sheet. Without :placeholder-shown the label simply stays floated once focused, which is still a labelled field.