Skip to content

Floating Label Input

A text input whose label floats from placeholder position into a caption on focus.

Freeminimaleditorial
Category
Components
Added
2026-08-22
Deps
none
Updated
2026-09-01
Preview

Props

No runtime dependencies
"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>
  );
}
Notes

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.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

Featured
NewPro

A numeric input whose label you drag to scrub, with pointer lock and precision modifiers.

inputnumberdrag
Featured
NewPro

A signature pad with real variable-width ink — pen pressure where it exists, velocity everywhere else, coalesced samples and midpoint quadratics.

canvasinputpointer
Featured
New

OTP Code Input

components

A one-time-code field whose cells survive SMS autofill, paste and Android backspace.

otpinputform