Floating label input
An Input whose label starts where the placeholder would sit and, on focus or once the field holds a value, shrinks into a band at the top of the same control. It never leaves the box, so a field occupies exactly its own height. Unlike a placeholder the label never disappears, so the field stays readable while it is being filled in.
It is an Input — same variants, background, focus ring and invalid halo, same props. Only the label and the optional trailing slot are added, so everything else composes the way it does for a plain Input. One deliberate exception: it stands taller than the plain control (60px against Input's 28, 40 and 48), because it has to hold two comfortable lines where a placeholder field holds one.
Use it where vertical space is tight and a stacked <Label> + Input would cost a row: address forms, checkout, onboarding steps. For everything else, prefer the plain Input inside a Field.
Examples
Basic
One size
There is no size: a floating field is always the 60px box, Airbnb's base input number for number. It is the control our long forms are built from, and a floating label needs the room; a compact field is a plain Input with a placeholder. The label wears the control's own text size, so at rest it is pixel-identical to the placeholder it stands in for; raised, it is a 12px label.
Appearance
Two surfaces, same as Input: the default grey for fields sitting on a card or a white page, and appearance="white" — the white card with the Cüte shadow — for the auth shell's grey background. Every text field on the join screens is the white one; a grey field there is a bug (see the join-flow doc).
Trailing icons
Whatever you pass to trailing is rendered in a real InputGroupAddon with align="inline-end", so it behaves exactly as it would in any other input group: correct inset, icons sized to the group, click-to-focus, and a <InputGroupButton> staying clickable. Nothing is overridden here — pass InputGroupButton and InputGroupText the way you would anywhere else, and let the addon size bare icons for you.
Required and optional
Same convention as Input: a Required badge, "Optional" as prose. required renders the badge inside the label, so it rides up with it instead of costing the extra row a floating label exists to save — and it still sets native validation and aria-required on the control.
Optional fields say so in a FieldDescription, which does cost a row. If a form is mostly optional fields, that is the wrong trade: use a plain Input with a stacked label there.
Invalid, disabled, read-only
Exactly the Input states, in both treatments. By default the control takes a tinted fill and a solid edge; stateStyle="halo" on the Field keeps it on the card surface with a soft outline and tucks the message into a panel behind it.
aria-invalid also turns the label destructive — that lives on the control, so it reads the same either way. Valid does not turn it. The message is a FieldError.
Forms
It spreads native input props, so react-hook-form's register works directly:
<Field orientation="vertical">
<FloatingLabelInput
label="Street and house number"
aria-invalid={!!errors.street}
{...register("street")}
/>
<FieldError errors={[errors.street]} />
</Field>Notes
labelis the accessible name — do not add a separate<Label>.- The label never renders outside the control, so no space is reserved above it and rows stack on their own gap alone.
- The lift is CSS (
:focusand:placeholder-shown), with no state behind it, so browser autofill,form.reset(), controlled and uncontrolled fields all move the label alike, and a pre-filled field renders raised without animating. - The lift is skipped under
prefers-reduced-motion. - A
placeholderonly appears once the label is out of the way.
Anatomy
FloatingLabelInput is the everyday composition of three parts, the way a Base UI component is built. Reach for the parts when the control is not an input:
<FloatingField required>
<FloatingFieldControl /> {/* an InputGroupInput, or render={…} */}
<FloatingFieldLabel>City</FloatingFieldLabel>
</FloatingField>FloatingFieldis the root: theInputGroupshell, the id both parts share,data-raisedfor the stylesheet.raisedlifts the label for a control that has no:focus/:placeholder-shownof its own.FloatingFieldControlis the control.rendermakes the caller's element (a button, a combobox input) wear the control's box and slot instead —FloatingLabelDatePickeris built this way.FloatingFieldLabelis the label; it links to the control through context and carries the Required badge.
API
Input's props, plus:
| Prop | Type | Description |
|---|---|---|
| label | string | The floating label. Doubles as the control's accessible name. |
| trailing | ReactNode | Icons, a spinner or a small button pinned to the control's right edge. The value's text is inset to clear it. |