Floating Label Input
A text input whose label floats from placeholder position into a caption on focus.
- Category
- Components
- Added
- 2026-08-22
- Deps
- none
- Updated
- 2026-09-01
Props
"use client";
import { useId, useState } from "react";
export function FloatingLabelInput({
label = "Email address",
type = "email",
}: {
label?: string;
type?: string;
}) {
const id = useId();
const [value, setValue] = useState("");
const [focused, setFocused] = useState(false);
const floated = focused || value.length > 0;
return (
<div className="relative w-72">
<input
id={id}
type={type}
value={value}
onChange={(e) => setValue(e.target.value)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
className="peer w-full rounded-xl border border-neutral-300 bg-white px-4 pb-2.5 pt-4 text-sm outline-none transition-colors focus:border-red-500"
/>
<label
htmlFor={id}
className={`pointer-events-none absolute left-4 top-1/2 origin-left -translate-y-1/2 text-sm transition-all duration-200 ease-out ${
floated ? "-translate-y-[22px] scale-75" : "scale-100"
} ${focused ? "text-red-500" : "text-neutral-500"}`}
>
{label}
</label>
</div>
);
}
About this component
The floating label pattern solves a real usability problem with plain placeholders: once someone starts typing, the placeholder vanishes and nothing above the field reminds them what it's for, which costs accuracy on longer forms. This implementation tracks focus and the input's own value in useState rather than leaning on the CSS-only peer-placeholder-shown trick, which works but only if the input carries a placeholder of a single space that most people copying the pattern forget to add, silently breaking the float. Floated here is focused OR value.length > 0, not just focused — check only focus and the label snaps back down over whatever was typed the instant the user tabs away, which reads as a bug even though the value itself is untouched. Label and input share an id from useId() rather than a hardcoded string, so two instances on one page never collide. Because it's a uniform translate plus scale, a plain CSS transition handles the motion — Framer Motion would be one more dependency doing a job className already does. The caveat: value is fully internal state, with no value or onChange prop exposed, so a parent can't control or reset the field without lifting that state itself.
Curator’s note
Floated is 'focused OR has a value', not just 'focused' — skip the value check and the label snaps back down over whatever the user typed the moment they tab away, which reads as broken even though the input itself is fine.
Pairs well with
Matched on shared tags and category — the entries most likely to be used alongside this one.
Scrub Number Field
componentsA numeric input whose label you drag to scrub, with pointer lock and precision modifiers.
Pressure Signature Pad
componentsA signature pad with real variable-width ink — pen pressure where it exists, velocity everywhere else, coalesced samples and midpoint quadratics.
OTP Code Input
componentsA one-time-code field whose cells survive SMS autofill, paste and Android backspace.