Home / Snippets / UI Components /

Floating label

A label that floats from placeholder position to the top edge on focus or when the input has a value — pure CSS, no JavaScript.

Widely Supported
uino-js

Quick implementation

.float-field {
  position: relative;
}

.float-field input {
  width: 100%;
  box-sizing: border-box;
  background: var(--card);
  border: 1px solid oklch(0.35 0.02 260);
  border-radius: 0.5rem;
  padding: 1.25rem 0.875rem 0.5rem;
  font-size: 1rem;
  color: var(--text);
  outline: none;
  transition: border-color 0.2s;
}

.float-field input:focus {
  border-color: var(--accent);
}

.float-field label {
  position: absolute;
  left: 0.875rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: 1rem;
  color: var(--muted);
  pointer-events: none;
  transition: all 0.2s ease;
  background: var(--card);
  padding: 0 0.2rem;
}

/* Float when focused OR when input has a value */
.float-field input:focus + label,
.float-field input:not(:placeholder-shown) + label {
  top: 0;
  transform: translateY(-50%);
  font-size: 0.75rem;
  color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .float-field label {
    transition: none;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building a CSS form component library.

Goal: A floating label input — label starts in the placeholder position and floats to the top edge on focus or when the field has a value. No JavaScript.

Technical constraints:
- Use position: relative on the wrapper (.float-field) and position: absolute on the label.
- Detect "filled" state using input:not(:placeholder-shown) — requires a space-only placeholder attribute on the input.
- Use the adjacent sibling combinator (+) so label must come AFTER input in the DOM.
- Animate with transition: all 0.2s ease on the label (top, font-size, color).
- Label background must match the card background (var(--card)) to cover the border on float.
- Use oklch() for all color values, not hex or rgba().
- Include @media (prefers-reduced-motion: reduce) to disable the transition.

Framework variant (pick one):
A) Vanilla CSS — wrapper div, then input, then label in that order.
B) React component — accept label text, type, id, and defaultValue as props.

Edge cases to handle:
- Input must have placeholder=" " (a single space) so :placeholder-shown works correctly.
- Label must never overlap the typed text — adjust padding-top on the input to make room.
- Works with type="email", type="password", and type="text".

Return HTML and CSS.

Why this matters in 2026

Floating labels reduce form height by combining the label and placeholder into one element — useful in dense checkout flows and settings panels. The pure CSS approach using :placeholder-shown and the adjacent sibling combinator achieves the same result as JavaScript-driven solutions without any event listeners or class toggling.

The logic

The trick relies on two CSS selectors. :not(:placeholder-shown) matches an input that has a value — the browser considers the placeholder hidden when the user types. :focus + label targets the label immediately after a focused input. In both states the label is moved to top: 0 and shrunk to font-size: 0.75rem. The label sits on top of the border by using a background matching the card colour and a small horizontal padding. The input uses asymmetric vertical padding (1.25rem top, 0.5rem bottom) to leave room for the floated label.

Accessibility & performance

Always pair the <input> with a real <label> linked via for/id — this is what makes the floating label approach accessible, unlike placeholder-only designs. Screen readers announce the label on focus regardless of its visual position. Add @media (prefers-reduced-motion: reduce) to disable the animation for users with vestibular sensitivity. The transition on the label runs on the compositor (opacity, transform) — if you change top it triggers layout, so keep durations short (under 200 ms).