30 Tailwind CSS Login Forms
03 / 30

All 30 in 30 Tailwind CSS Login Forms

  1. 01Tailwind Split-Screen Brand Hero Login Form
  2. 02Tailwind Multi-Tenant Workspace Account Switcher
  3. 03Tailwind Floating Label Login Form with peer Variants
  4. 04Tailwind Web3 Wallet Connect Login Grid
  5. 05Tailwind Minimalist Fintech Login Card with Layered Shadow
  6. 06Tailwind OAuth Social Login Form with Brand SVG Buttons
  7. 07Tailwind Passwordless Magic Link Login with data-state Machine
  8. 08Tailwind Aurora Glow Login Card with Drifting Blur Blobs
  9. 09Tailwind Neon Synthwave Arcade Login Form
  10. 10Tailwind Sliding Login and Sign Up Panel with peer-checked
  11. 11Tailwind 3D Flip Login Card with Password Reset Back Face
  12. 12Tailwind Terminal CLI Prompt Login Form
  13. 13Tailwind Floating Pill Login Widget Docked Over App Content
  14. 14Tailwind Gradient Border Login Card with background-clip
  15. 15Tailwind Slide-Over Side Panel Login without JavaScript
  16. 16Tailwind Social-First Sign Up with Brand-Compliant Buttons
  17. 17Tailwind Animated Mascot Login that Covers Its Eyes
  18. 18Tailwind Accessible Compact Login Modal with Scale-In
  19. 19Tailwind Glassmorphism Login Card with backdrop-filter
  20. 20Tailwind Multi-Step Login Wizard with Radio-Driven Track
  21. 21Tailwind Animated Conic Gradient Border Login Card
  22. 22Tailwind Secure Vault Login with user-invalid Error State
  23. 23Tailwind Neo-Brutalist Login Form with Hard Offset Shadow
  24. 24Tailwind Show Hide Password Toggle with aria-pressed
  25. 25Tailwind OTP Code Input Grid with Auto-Advance
  26. 26Tailwind Password Strength Meter with data-score Variants
  27. 27Tailwind Numeric PIN Pad Login for Kiosks and POS
  28. 28Tailwind Inline Form Validation with user-valid and user-invalid
  29. 29Tailwind Particle Constellation Login over Canvas
  30. 30Tailwind Biometric Face ID Prompt with Pulse Ripples
Pure TailwindMIT licensed

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.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

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>
@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.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@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;
}
3Fonts used by this demooptional — skip if you have your own type stack
<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) + label has no shorthand variant, so it becomes peer-[:not(:placeholder-shown)]: — the arbitrary peer form, and the single most useful thing to know about porting floating labels.
  • focus-within:[&>input]:border-tlf03-acc on 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 own focus-visible: and a wrapper rule.
  • transition-[translate,scale,color] rather than transition-transform, because v4 splits transforms into separate translate / scale properties — 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 #8695a0 to oklch(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.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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 &mdash; 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-4 and scale-75, one number each.
  • Boxed instead of underlined: swap border-b-2 for border rounded-xl and add px-3.5 to both input and label offsets.
  • Accent: --color-tlf03-acc drives underline, floated label, checkbox and button.
  • Field height is h-14; the label's resting top-[18px] moves with it.

Gotchas — read before shipping

  • placeholder=" " with a literal space is mandatory. An absent or empty placeholder makes :placeholder-shown match forever and the label never floats.
  • peer only 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

ChromeSafariFirefoxEdge
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.

Techniques used in this demo

Search CodeFronts

Loading…