# Input Floating Label — React Input Component

> Text fields whose label rests as a placeholder, then springs up into a small caption on focus or when filled; inset and underline variants.

- Category: [Inputs](https://pixelpapa.com/components/inputs.md)
- Tags: input, label, floating, form, focus
- Dependencies: lucide-react (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/inputs/input-floating-label

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/input-floating-label.json"
```

The file is written to `components/pixelpapa/input-floating-label.tsx`. To install manually, run `npm install lucide-react` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { AtSign, Building2, User } from "lucide-react";
import { useId, type ComponentProps, type ReactNode } from "react";
import { cn } from "@/lib/utils";

type FloatingInputProps = Omit<ComponentProps<"input">, "placeholder"> & {
  label: string;
  variant?: "inset" | "underline";
  /** Helper text under the field. */
  hint?: string;
  /** Error message; also flips the field to its invalid style. */
  error?: string;
  icon?: ReactNode;
};

export function FloatingInput({
  label,
  variant = "inset",
  hint,
  error,
  icon,
  id,
  className,
  ...props
}: FloatingInputProps) {
  const auto = useId();
  const fieldId = id ?? auto;
  const note = error ?? hint;
  const inset = variant === "inset";

  return (
    <div className={cn("w-full", className)}>
      <div
        className={cn(
          "relative flex h-14 items-center transition-[border-color,box-shadow,background-color] duration-200",
          inset
            ? "rounded-xl border bg-background shadow-xs focus-within:ring-4"
            : "border-b bg-transparent focus-within:bg-muted/40",
          error
            ? "border-destructive focus-within:ring-destructive/15"
            : "border-input focus-within:ring-ring/15",
          inset && !error && "focus-within:border-ring",
        )}
      >
        {icon && (
          <span
            aria-hidden
            className={cn("pointer-events-none absolute text-muted-foreground [&>svg]:size-4", inset ? "left-3.5" : "left-1")}
          >
            {icon}
          </span>
        )}
        {/* The single-space placeholder lets :placeholder-shown tell us if the field is empty. */}
        <input
          id={fieldId}
          placeholder=" "
          aria-invalid={error ? true : undefined}
          aria-describedby={note ? `${fieldId}-note` : undefined}
          className={cn(
            "peer h-full w-full bg-transparent pt-5 pb-1 text-sm text-foreground outline-none",
            inset ? "rounded-xl pr-3.5" : "pr-1",
            icon ? (inset ? "pl-10" : "pl-7") : inset ? "pl-3.5" : "pl-1",
          )}
          {...props}
        />
        <label
          htmlFor={fieldId}
          className={cn(
            "pointer-events-none absolute top-1/2 origin-left -translate-y-1/2 text-sm text-muted-foreground",
            "transition-[translate,scale,color] duration-300 ease-[cubic-bezier(0.34,1.4,0.64,1)] motion-reduce:transition-none",
            "peer-focus:-translate-y-[1.4rem] peer-focus:scale-[0.78]",
            "peer-[&:not(:placeholder-shown)]:-translate-y-[1.4rem] peer-[&:not(:placeholder-shown)]:scale-[0.78]",
            error ? "text-destructive" : "peer-focus:text-primary",
            icon ? (inset ? "left-10" : "left-7") : inset ? "left-3.5" : "left-1",
          )}
        >
          {label}
        </label>
        {!inset && (
          <span
            aria-hidden
            className={cn(
              "absolute inset-x-0 -bottom-px h-0.5 origin-center scale-x-0 rounded-full transition-transform duration-300 ease-out peer-focus:scale-x-100",
              error ? "bg-destructive" : "bg-primary",
            )}
          />
        )}
      </div>
      {note && (
        <p id={`${fieldId}-note`} className={cn("mt-1.5 px-1 text-xs", error ? "text-destructive" : "text-muted-foreground")}>
          {note}
        </p>
      )}
    </div>
  );
}

export default function InputFloatingLabelDemo() {
  return (
    <div className="grid w-full max-w-2xl gap-5 sm:grid-cols-2">
      <form
        onSubmit={(e) => e.preventDefault()}
        className="space-y-3.5 rounded-2xl border border-border bg-card p-6 shadow-sm"
      >
        <p className="text-sm font-medium text-card-foreground">Create your workspace</p>
        <FloatingInput label="Full name" defaultValue="Mira Okafor" autoComplete="name" icon={<User />} />
        <FloatingInput label="Work email" type="email" autoComplete="email" icon={<AtSign />} />
        <FloatingInput
          label="Team size"
          inputMode="numeric"
          defaultValue="0"
          error="Enter a number between 1 and 500."
        />
      </form>
      <form
        onSubmit={(e) => e.preventDefault()}
        className="space-y-3.5 rounded-2xl border border-border bg-card p-6 shadow-sm"
      >
        <p className="text-sm font-medium text-card-foreground">Billing details</p>
        <FloatingInput variant="underline" label="Company" defaultValue="Halden Studio" icon={<Building2 />} />
        <FloatingInput variant="underline" label="VAT number" hint="Optional. Shown on invoices." />
        <FloatingInput variant="underline" label="Postal code" autoComplete="postal-code" />
      </form>
    </div>
  );
}
```

## AI prompt

```text
Add the "Input Floating Label" component from PixelPapa UI to this project.

What it is: Text fields whose label rests as a placeholder, then springs up into a small caption on focus or when filled; inset and underline variants.
Category: Inputs. Depends on: lucide-react.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/input-floating-label.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/input-floating-label.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/input-floating-label" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More inputs

- [Input Credit Card](https://pixelpapa.com/components/inputs/input-credit-card.md) (Pro): A payment form with a live card preview: digits group in fours, a highlight glides to the part you are editing, and the card flips over for the security code.
- [Input Currency Format](https://pixelpapa.com/components/inputs/input-currency-format.md) (Pro): A large amount field that groups thousands as you type without losing the caret, pads cents on blur, and grows to hug its digits.
- [Input Inline Edit](https://pixelpapa.com/components/inputs/input-inline-edit.md): Text that turns into a field in place with no layout shift; Enter or blur saves with a brief tick, Escape puts the old value back.
- [Input Mention Autocomplete](https://pixelpapa.com/components/inputs/input-mention-autocomplete.md) (Pro): A comment box that opens a people list when you type @, filters as you go, inserts the handle with Enter or Tab and tints finished mentions inline.
- [Input Number Stepper](https://pixelpapa.com/components/inputs/input-number-stepper.md) (Pro): A spinbutton whose digits roll up or down like an odometer; hold a button to repeat with acceleration, or use the arrow keys.
- [Input OTP Boxes](https://pixelpapa.com/components/inputs/input-otp-boxes.md) (Pro): One-time-code boxes that auto-advance, accept a pasted code, step back on Backspace, pop each digit in and shake or hop on error or success.
