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.
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).