01 / 16
All 16 in 16 Tailwind CSS Floating Label Inputs
- 01Tailwind Staggered Letter-Cascade Floating Label
- 02Tailwind Gradient-Ink Floating Label with Animated Underline Draw
- 03Tailwind Conic Border-Beam Floating Label (@property Animation)
- 04Pure Tailwind Floating Label Input (No JavaScript)
- 05Tailwind Floating Label with Chrome Autofill Fix
- 06Tailwind Floating Label Validation States (Error + Success)
- 07Accessible Tailwind Floating Label Input (ARIA + WCAG)
- 08Tailwind Floating Label for Select Dropdowns
- 09Tailwind Material Design Outlined Text Field
- 10Tailwind Filled & Underline Floating Label Input
- 11Tailwind Dark Mode Floating Label Input with light-dark()
- 12Tailwind Input with Icon Inside and Floating Label
- 13Responsive Tailwind Floating Label for Mobile (No iOS Zoom)
- 14Tailwind Auto-Growing Textarea with Floating Label
- 15Tailwind Password Input with Show/Hide Toggle and Floating Label
- 16Tailwind Multi-Column Form Grid with Floating Labels
Tailwind Staggered Letter-Cascade Floating Label
Each letter of the label rises on its own, a left-to-right wave: one *: class list styles every letter span, *:[transition-delay:calc(var(--i)*30ms)] staggers them, and group-focus-within: + group-has-[input:not(:placeholder-shown)]: fire the float. The real label text sits in an sr-only span so screen readers hear a word, not letters.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tfl-01 w-full min-h-svh grid place-items-center bg-tfl01-bg bg-[radial-gradient(90%_70%_at_50%_0%,var(--color-tfl01-glow),transparent_70%)] px-5 py-12 font-tfl01-sans" aria-label="Staggered letter cascade floating label demo">
<form class="flex w-full max-w-[380px] flex-col gap-5 rounded-3xl bg-white p-8 shadow-[0_24px_60px_-32px_oklch(0.35_0.12_320/0.45)] ring-1 ring-tfl01-ink/5" novalidate>
<div class="mb-1 grid gap-1">
<h3 class="text-2xl font-extrabold tracking-[-0.02em] text-tfl01-ink">Join the studio</h3>
<p class="text-sm text-tfl01-mut">Focus a field and watch the label ripple.</p>
</div>
<div class="group relative">
<input class="peer h-14 w-full rounded-xl border-[1.5px] border-tfl01-line bg-white px-4 pt-[22px] pb-1.5 text-base text-tfl01-ink outline-hidden transition-[border-color,box-shadow] duration-200 hover:border-tfl01-line-hover focus:border-tfl01-acc focus:ring-4 focus:ring-tfl01-acc/15" type="text" id="tfl-01-name" name="name" placeholder=" " autocomplete="name">
<label class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-base text-tfl01-mut *:inline-block *:origin-bottom-left *:transition-[translate,scale,color] *:duration-[340ms] *:ease-[cubic-bezier(.3,1.6,.4,1)] *:[transition-delay:calc(var(--i)*30ms)] group-focus-within:*:-translate-y-3.5 group-focus-within:*:scale-72 group-focus-within:*:text-tfl01-acc group-has-[input:not(:placeholder-shown)]:*:-translate-y-3.5 group-has-[input:not(:placeholder-shown)]:*:scale-72 motion-reduce:*:[transition-delay:0s]" for="tfl-01-name">
<span class="sr-only">Your name</span>
<span aria-hidden="true" style="--i:0">Y</span><span aria-hidden="true" style="--i:1">o</span><span aria-hidden="true" style="--i:2">u</span><span aria-hidden="true" style="--i:3">r</span><span aria-hidden="true" style="--i:4"> </span><span aria-hidden="true" style="--i:5">n</span><span aria-hidden="true" style="--i:6">a</span><span aria-hidden="true" style="--i:7">m</span><span aria-hidden="true" style="--i:8">e</span>
</label>
</div>
<div class="group relative">
<input class="peer h-14 w-full rounded-xl border-[1.5px] border-tfl01-line bg-white px-4 pt-[22px] pb-1.5 text-base text-tfl01-ink outline-hidden transition-[border-color,box-shadow] duration-200 hover:border-tfl01-line-hover focus:border-tfl01-acc focus:ring-4 focus:ring-tfl01-acc/15" type="email" id="tfl-01-mail" name="email" placeholder=" " autocomplete="email">
<label class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-base text-tfl01-mut *:inline-block *:origin-bottom-left *:transition-[translate,scale,color] *:duration-[340ms] *:ease-[cubic-bezier(.3,1.6,.4,1)] *:[transition-delay:calc(var(--i)*30ms)] group-focus-within:*:-translate-y-3.5 group-focus-within:*:scale-72 group-focus-within:*:text-tfl01-acc group-has-[input:not(:placeholder-shown)]:*:-translate-y-3.5 group-has-[input:not(:placeholder-shown)]:*:scale-72 motion-reduce:*:[transition-delay:0s]" for="tfl-01-mail">
<span class="sr-only">Email</span>
<span aria-hidden="true" style="--i:0">E</span><span aria-hidden="true" style="--i:1">m</span><span aria-hidden="true" style="--i:2">a</span><span aria-hidden="true" style="--i:3">i</span><span aria-hidden="true" style="--i:4">l</span>
</label>
</div>
<button class="mt-1 min-h-12 cursor-pointer rounded-xl bg-tfl01-acc text-[15px] font-bold text-white shadow-[0_10px_24px_-12px_var(--color-tfl01-acc)] transition-[filter,scale] duration-150 hover:brightness-110 active:scale-98 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tfl01-acc/50" type="button">Apply</button>
</form>
</section><section class="tfl-01 w-full min-h-svh grid place-items-center bg-tfl01-bg bg-[radial-gradient(90%_70%_at_50%_0%,var(--color-tfl01-glow),transparent_70%)] px-5 py-12 font-tfl01-sans" aria-label="Staggered letter cascade floating label demo">
<form class="flex w-full max-w-[380px] flex-col gap-5 rounded-3xl bg-white p-8 shadow-[0_24px_60px_-32px_oklch(0.35_0.12_320/0.45)] ring-1 ring-tfl01-ink/5" novalidate>
<div class="mb-1 grid gap-1">
<h3 class="text-2xl font-extrabold tracking-[-0.02em] text-tfl01-ink">Join the studio</h3>
<p class="text-sm text-tfl01-mut">Focus a field and watch the label ripple.</p>
</div>
<div class="group relative">
<input class="peer h-14 w-full rounded-xl border-[1.5px] border-tfl01-line bg-white px-4 pt-[22px] pb-1.5 text-base text-tfl01-ink outline-hidden transition-[border-color,box-shadow] duration-200 hover:border-tfl01-line-hover focus:border-tfl01-acc focus:ring-4 focus:ring-tfl01-acc/15" type="text" id="tfl-01-name" name="name" placeholder=" " autocomplete="name">
<label class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-base text-tfl01-mut *:inline-block *:origin-bottom-left *:transition-[translate,scale,color] *:duration-[340ms] *:ease-[cubic-bezier(.3,1.6,.4,1)] *:[transition-delay:calc(var(--i)*30ms)] group-focus-within:*:-translate-y-3.5 group-focus-within:*:scale-72 group-focus-within:*:text-tfl01-acc group-has-[input:not(:placeholder-shown)]:*:-translate-y-3.5 group-has-[input:not(:placeholder-shown)]:*:scale-72 motion-reduce:*:[transition-delay:0s]" for="tfl-01-name">
<span class="sr-only">Your name</span>
<span aria-hidden="true" style="--i:0">Y</span><span aria-hidden="true" style="--i:1">o</span><span aria-hidden="true" style="--i:2">u</span><span aria-hidden="true" style="--i:3">r</span><span aria-hidden="true" style="--i:4"> </span><span aria-hidden="true" style="--i:5">n</span><span aria-hidden="true" style="--i:6">a</span><span aria-hidden="true" style="--i:7">m</span><span aria-hidden="true" style="--i:8">e</span>
</label>
</div>
<div class="group relative">
<input class="peer h-14 w-full rounded-xl border-[1.5px] border-tfl01-line bg-white px-4 pt-[22px] pb-1.5 text-base text-tfl01-ink outline-hidden transition-[border-color,box-shadow] duration-200 hover:border-tfl01-line-hover focus:border-tfl01-acc focus:ring-4 focus:ring-tfl01-acc/15" type="email" id="tfl-01-mail" name="email" placeholder=" " autocomplete="email">
<label class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-base text-tfl01-mut *:inline-block *:origin-bottom-left *:transition-[translate,scale,color] *:duration-[340ms] *:ease-[cubic-bezier(.3,1.6,.4,1)] *:[transition-delay:calc(var(--i)*30ms)] group-focus-within:*:-translate-y-3.5 group-focus-within:*:scale-72 group-focus-within:*:text-tfl01-acc group-has-[input:not(:placeholder-shown)]:*:-translate-y-3.5 group-has-[input:not(:placeholder-shown)]:*:scale-72 motion-reduce:*:[transition-delay:0s]" for="tfl-01-mail">
<span class="sr-only">Email</span>
<span aria-hidden="true" style="--i:0">E</span><span aria-hidden="true" style="--i:1">m</span><span aria-hidden="true" style="--i:2">a</span><span aria-hidden="true" style="--i:3">i</span><span aria-hidden="true" style="--i:4">l</span>
</label>
</div>
<button class="mt-1 min-h-12 cursor-pointer rounded-xl bg-tfl01-acc text-[15px] font-bold text-white shadow-[0_10px_24px_-12px_var(--color-tfl01-acc)] transition-[filter,scale] duration-150 hover:brightness-110 active:scale-98 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tfl01-acc/50" type="button">Apply</button>
</form>
</section>@import "tailwindcss";
@theme {
--color-tfl01-bg: #f6eff6;
--color-tfl01-bg: oklch(0.96 0.012 320);
--color-tfl01-glow: #fff9ff;
--color-tfl01-glow: oklch(0.99 0.008 320);
--color-tfl01-ink: #241a24;
--color-tfl01-ink: oklch(0.23 0.025 320);
--color-tfl01-mut: #6f5e73;
--color-tfl01-mut: oklch(0.5 0.03 315);
--color-tfl01-line: #e3d5e3;
--color-tfl01-line: oklch(0.88 0.02 320);
--color-tfl01-line-hover: #c9b5ca;
--color-tfl01-line-hover: oklch(0.79 0.03 320);
--color-tfl01-acc: #b43aa9;
--color-tfl01-acc: oklch(0.55 0.19 325);
--font-tfl01-sans: 'Bricolage Grotesque', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tfl-01 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tfl01-bg: #f6eff6;
--color-tfl01-bg: oklch(0.96 0.012 320);
--color-tfl01-glow: #fff9ff;
--color-tfl01-glow: oklch(0.99 0.008 320);
--color-tfl01-ink: #241a24;
--color-tfl01-ink: oklch(0.23 0.025 320);
--color-tfl01-mut: #6f5e73;
--color-tfl01-mut: oklch(0.5 0.03 315);
--color-tfl01-line: #e3d5e3;
--color-tfl01-line: oklch(0.88 0.02 320);
--color-tfl01-line-hover: #c9b5ca;
--color-tfl01-line-hover: oklch(0.79 0.03 320);
--color-tfl01-acc: #b43aa9;
--color-tfl01-acc: oklch(0.55 0.19 325);
--font-tfl01-sans: 'Bricolage Grotesque', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tfl-01 {
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-tfl01-bg: #f6eff6;
--color-tfl01-bg: oklch(0.96 0.012 320);
--color-tfl01-glow: #fff9ff;
--color-tfl01-glow: oklch(0.99 0.008 320);
--color-tfl01-ink: #241a24;
--color-tfl01-ink: oklch(0.23 0.025 320);
--color-tfl01-mut: #6f5e73;
--color-tfl01-mut: oklch(0.5 0.03 315);
--color-tfl01-line: #e3d5e3;
--color-tfl01-line: oklch(0.88 0.02 320);
--color-tfl01-line-hover: #c9b5ca;
--color-tfl01-line-hover: oklch(0.79 0.03 320);
--color-tfl01-acc: #b43aa9;
--color-tfl01-acc: oklch(0.55 0.19 325);
--font-tfl01-sans: 'Bricolage Grotesque', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tfl-01 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tfl01-bg: #f6eff6;
--color-tfl01-bg: oklch(0.96 0.012 320);
--color-tfl01-glow: #fff9ff;
--color-tfl01-glow: oklch(0.99 0.008 320);
--color-tfl01-ink: #241a24;
--color-tfl01-ink: oklch(0.23 0.025 320);
--color-tfl01-mut: #6f5e73;
--color-tfl01-mut: oklch(0.5 0.03 315);
--color-tfl01-line: #e3d5e3;
--color-tfl01-line: oklch(0.88 0.02 320);
--color-tfl01-line-hover: #c9b5ca;
--color-tfl01-line-hover: oklch(0.79 0.03 320);
--color-tfl01-acc: #b43aa9;
--color-tfl01-acc: oklch(0.55 0.19 325);
--font-tfl01-sans: 'Bricolage Grotesque', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tfl-01 {
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=Bricolage+Grotesque:opsz,wght@12..96,400..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=Bricolage+Grotesque:opsz,wght@12..96,400..800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Nine
.flb-01__chclass attributes collapsed into one*:class list on the label — the letters now carry only their--iindex. - The sibling selector
input:focus ~ label .chbecamegroup-focus-within:*:on the field, becausepeer-*cannot reach grandchildren. - UI upgrade from the CSS original: the resting label rose from
#96829b(3.6:1, failing) tooklch(0.5)(5.6:1), the accent darkened so white button text passes AA, and the form gained a one-line instruction so the effect is discoverable. - UI upgrade: Bricolage Grotesque replaces Segoe UI;
outline-hiddenreplacesoutline:none, so Windows High Contrast still paints a focus outline.
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 Staggered Letter-Cascade Floating Label
Source: https://codefronts.com/components/tailwind-css-floating-label-inputs/tailwind-staggered-letter-cascade-floating-label/
Each letter of the label rises on its own, a left-to-right wave: one *: class list styles every letter span, *:[transition-delay:calc(var(--i)*30ms)] staggers them, and group-focus-within: + group-has-[input:not(:placeholder-shown)]: fire the float. The real label text sits in an sr-only span so screen readers hear a word, not letters.
## HTML
```html
<section class="tfl-01 w-full min-h-svh grid place-items-center bg-tfl01-bg bg-[radial-gradient(90%_70%_at_50%_0%,var(--color-tfl01-glow),transparent_70%)] px-5 py-12 font-tfl01-sans" aria-label="Staggered letter cascade floating label demo">
<form class="flex w-full max-w-[380px] flex-col gap-5 rounded-3xl bg-white p-8 shadow-[0_24px_60px_-32px_oklch(0.35_0.12_320/0.45)] ring-1 ring-tfl01-ink/5" novalidate>
<div class="mb-1 grid gap-1">
<h3 class="text-2xl font-extrabold tracking-[-0.02em] text-tfl01-ink">Join the studio</h3>
<p class="text-sm text-tfl01-mut">Focus a field and watch the label ripple.</p>
</div>
<div class="group relative">
<input class="peer h-14 w-full rounded-xl border-[1.5px] border-tfl01-line bg-white px-4 pt-[22px] pb-1.5 text-base text-tfl01-ink outline-hidden transition-[border-color,box-shadow] duration-200 hover:border-tfl01-line-hover focus:border-tfl01-acc focus:ring-4 focus:ring-tfl01-acc/15" type="text" id="tfl-01-name" name="name" placeholder=" " autocomplete="name">
<label class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-base text-tfl01-mut *:inline-block *:origin-bottom-left *:transition-[translate,scale,color] *:duration-[340ms] *:ease-[cubic-bezier(.3,1.6,.4,1)] *:[transition-delay:calc(var(--i)*30ms)] group-focus-within:*:-translate-y-3.5 group-focus-within:*:scale-72 group-focus-within:*:text-tfl01-acc group-has-[input:not(:placeholder-shown)]:*:-translate-y-3.5 group-has-[input:not(:placeholder-shown)]:*:scale-72 motion-reduce:*:[transition-delay:0s]" for="tfl-01-name">
<span class="sr-only">Your name</span>
<span aria-hidden="true" style="--i:0">Y</span><span aria-hidden="true" style="--i:1">o</span><span aria-hidden="true" style="--i:2">u</span><span aria-hidden="true" style="--i:3">r</span><span aria-hidden="true" style="--i:4"> </span><span aria-hidden="true" style="--i:5">n</span><span aria-hidden="true" style="--i:6">a</span><span aria-hidden="true" style="--i:7">m</span><span aria-hidden="true" style="--i:8">e</span>
</label>
</div>
<div class="group relative">
<input class="peer h-14 w-full rounded-xl border-[1.5px] border-tfl01-line bg-white px-4 pt-[22px] pb-1.5 text-base text-tfl01-ink outline-hidden transition-[border-color,box-shadow] duration-200 hover:border-tfl01-line-hover focus:border-tfl01-acc focus:ring-4 focus:ring-tfl01-acc/15" type="email" id="tfl-01-mail" name="email" placeholder=" " autocomplete="email">
<label class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-base text-tfl01-mut *:inline-block *:origin-bottom-left *:transition-[translate,scale,color] *:duration-[340ms] *:ease-[cubic-bezier(.3,1.6,.4,1)] *:[transition-delay:calc(var(--i)*30ms)] group-focus-within:*:-translate-y-3.5 group-focus-within:*:scale-72 group-focus-within:*:text-tfl01-acc group-has-[input:not(:placeholder-shown)]:*:-translate-y-3.5 group-has-[input:not(:placeholder-shown)]:*:scale-72 motion-reduce:*:[transition-delay:0s]" for="tfl-01-mail">
<span class="sr-only">Email</span>
<span aria-hidden="true" style="--i:0">E</span><span aria-hidden="true" style="--i:1">m</span><span aria-hidden="true" style="--i:2">a</span><span aria-hidden="true" style="--i:3">i</span><span aria-hidden="true" style="--i:4">l</span>
</label>
</div>
<button class="mt-1 min-h-12 cursor-pointer rounded-xl bg-tfl01-acc text-[15px] font-bold text-white shadow-[0_10px_24px_-12px_var(--color-tfl01-acc)] transition-[filter,scale] duration-150 hover:brightness-110 active:scale-98 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tfl01-acc/50" type="button">Apply</button>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tfl01-bg: #f6eff6;
--color-tfl01-bg: oklch(0.96 0.012 320);
--color-tfl01-glow: #fff9ff;
--color-tfl01-glow: oklch(0.99 0.008 320);
--color-tfl01-ink: #241a24;
--color-tfl01-ink: oklch(0.23 0.025 320);
--color-tfl01-mut: #6f5e73;
--color-tfl01-mut: oklch(0.5 0.03 315);
--color-tfl01-line: #e3d5e3;
--color-tfl01-line: oklch(0.88 0.02 320);
--color-tfl01-line-hover: #c9b5ca;
--color-tfl01-line-hover: oklch(0.79 0.03 320);
--color-tfl01-acc: #b43aa9;
--color-tfl01-acc: oklch(0.55 0.19 325);
--font-tfl01-sans: 'Bricolage Grotesque', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tfl-01 {
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 Staggered Letter-Cascade Floating Label
Source: https://codefronts.com/components/tailwind-css-floating-label-inputs/tailwind-staggered-letter-cascade-floating-label/
Each letter of the label rises on its own, a left-to-right wave: one *: class list styles every letter span, *:[transition-delay:calc(var(--i)*30ms)] staggers them, and group-focus-within: + group-has-[input:not(:placeholder-shown)]: fire the float. The real label text sits in an sr-only span so screen readers hear a word, not letters.
## HTML
```html
<section class="tfl-01 w-full min-h-svh grid place-items-center bg-tfl01-bg bg-[radial-gradient(90%_70%_at_50%_0%,var(--color-tfl01-glow),transparent_70%)] px-5 py-12 font-tfl01-sans" aria-label="Staggered letter cascade floating label demo">
<form class="flex w-full max-w-[380px] flex-col gap-5 rounded-3xl bg-white p-8 shadow-[0_24px_60px_-32px_oklch(0.35_0.12_320/0.45)] ring-1 ring-tfl01-ink/5" novalidate>
<div class="mb-1 grid gap-1">
<h3 class="text-2xl font-extrabold tracking-[-0.02em] text-tfl01-ink">Join the studio</h3>
<p class="text-sm text-tfl01-mut">Focus a field and watch the label ripple.</p>
</div>
<div class="group relative">
<input class="peer h-14 w-full rounded-xl border-[1.5px] border-tfl01-line bg-white px-4 pt-[22px] pb-1.5 text-base text-tfl01-ink outline-hidden transition-[border-color,box-shadow] duration-200 hover:border-tfl01-line-hover focus:border-tfl01-acc focus:ring-4 focus:ring-tfl01-acc/15" type="text" id="tfl-01-name" name="name" placeholder=" " autocomplete="name">
<label class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-base text-tfl01-mut *:inline-block *:origin-bottom-left *:transition-[translate,scale,color] *:duration-[340ms] *:ease-[cubic-bezier(.3,1.6,.4,1)] *:[transition-delay:calc(var(--i)*30ms)] group-focus-within:*:-translate-y-3.5 group-focus-within:*:scale-72 group-focus-within:*:text-tfl01-acc group-has-[input:not(:placeholder-shown)]:*:-translate-y-3.5 group-has-[input:not(:placeholder-shown)]:*:scale-72 motion-reduce:*:[transition-delay:0s]" for="tfl-01-name">
<span class="sr-only">Your name</span>
<span aria-hidden="true" style="--i:0">Y</span><span aria-hidden="true" style="--i:1">o</span><span aria-hidden="true" style="--i:2">u</span><span aria-hidden="true" style="--i:3">r</span><span aria-hidden="true" style="--i:4"> </span><span aria-hidden="true" style="--i:5">n</span><span aria-hidden="true" style="--i:6">a</span><span aria-hidden="true" style="--i:7">m</span><span aria-hidden="true" style="--i:8">e</span>
</label>
</div>
<div class="group relative">
<input class="peer h-14 w-full rounded-xl border-[1.5px] border-tfl01-line bg-white px-4 pt-[22px] pb-1.5 text-base text-tfl01-ink outline-hidden transition-[border-color,box-shadow] duration-200 hover:border-tfl01-line-hover focus:border-tfl01-acc focus:ring-4 focus:ring-tfl01-acc/15" type="email" id="tfl-01-mail" name="email" placeholder=" " autocomplete="email">
<label class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-base text-tfl01-mut *:inline-block *:origin-bottom-left *:transition-[translate,scale,color] *:duration-[340ms] *:ease-[cubic-bezier(.3,1.6,.4,1)] *:[transition-delay:calc(var(--i)*30ms)] group-focus-within:*:-translate-y-3.5 group-focus-within:*:scale-72 group-focus-within:*:text-tfl01-acc group-has-[input:not(:placeholder-shown)]:*:-translate-y-3.5 group-has-[input:not(:placeholder-shown)]:*:scale-72 motion-reduce:*:[transition-delay:0s]" for="tfl-01-mail">
<span class="sr-only">Email</span>
<span aria-hidden="true" style="--i:0">E</span><span aria-hidden="true" style="--i:1">m</span><span aria-hidden="true" style="--i:2">a</span><span aria-hidden="true" style="--i:3">i</span><span aria-hidden="true" style="--i:4">l</span>
</label>
</div>
<button class="mt-1 min-h-12 cursor-pointer rounded-xl bg-tfl01-acc text-[15px] font-bold text-white shadow-[0_10px_24px_-12px_var(--color-tfl01-acc)] transition-[filter,scale] duration-150 hover:brightness-110 active:scale-98 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tfl01-acc/50" type="button">Apply</button>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tfl01-bg: #f6eff6;
--color-tfl01-bg: oklch(0.96 0.012 320);
--color-tfl01-glow: #fff9ff;
--color-tfl01-glow: oklch(0.99 0.008 320);
--color-tfl01-ink: #241a24;
--color-tfl01-ink: oklch(0.23 0.025 320);
--color-tfl01-mut: #6f5e73;
--color-tfl01-mut: oklch(0.5 0.03 315);
--color-tfl01-line: #e3d5e3;
--color-tfl01-line: oklch(0.88 0.02 320);
--color-tfl01-line-hover: #c9b5ca;
--color-tfl01-line-hover: oklch(0.79 0.03 320);
--color-tfl01-acc: #b43aa9;
--color-tfl01-acc: oklch(0.55 0.19 325);
--font-tfl01-sans: 'Bricolage Grotesque', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tfl-01 {
width: 100%;
min-height: 100svh;
}
```How this works
Why group, not peer. peer-* only reaches siblings of the input; the letters are grandchildren of the label. The field wrapper becomes the group, and two variants cover both float triggers:
group-focus-within:*:-translate-y-3.5
group-has-[input:not(:placeholder-shown)]:*:-translate-y-3.5The child variant does the repetition. The CSS original put a class on all nine letter spans; *: targets direct children, so the letters carry only style="--i:N".
The spring is *:ease-[cubic-bezier(.3,1.6,.4,1)] — the letters overshoot and settle, and the un-float plays the wave back automatically.
Make it yours
- Wave speed: the
30msmultiplier in*:[transition-delay:…]. - Bounce: the second bezier value (
1.6) — higher is springier. - Reverse the wave by numbering
--iright-to-left. - Re-theme: edit
--color-tfl01-acc; letters, ring and button follow.
Gotchas — read before shipping
- The letter run MUST be
aria-hidden="true"with the real text insr-only, or screen readers spell it out. - Keep labels under ~15 characters; cap total delay under ~450ms.
- Spaces collapse inside inline-block spans — use
for the gap.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:has() and the *: child variant are within Tailwind v4's own floor. Under reduced motion, delays zero out and all letters float together.