Using CSS :placeholder-shown for Floating Labels

The CSS :placeholder-shown pseudo-class provides a pure-CSS method for implementing floating label patterns by detecting whether an input field currently contains text. Historically, floating labels required JavaScript to track user input and toggle CSS classes to keep labels elevated when fields were populated. By pairing :placeholder-shown with adjacent sibling combinators, developers can build robust, high-performance form animations entirely within CSS, reducing script overhead and improving user experience.

The Floating Label Mechanism

A floating label sits inside an input field like placeholder text when the field is empty. When the user focuses on the field or enters a value, the label scales down and transitions to the top edge of the input.

The primary challenge of pure-CSS floating labels is determining whether the input has user-entered content while the field is blurred. While the :focus pseudo-class handles the active state, it cannot detect persistent values. The :placeholder-shown pseudo-class solves this by targeting any form control that is currently rendering its placeholder text.

When the field is empty, the placeholder is visible, triggering :placeholder-shown. Once the user types even a single character, the browser hides the placeholder, and the :placeholder-shown state is dropped.

Structural Requirements in HTML

To manipulate the label based on the input's state, the <input> element must precede the <label> element in the DOM tree. This ordering allows the use of the adjacent sibling combinator (+). Additionally, the input must have a non-empty placeholder attribute to activate the selector; a single whitespace character (placeholder=" ") is often used so the native placeholder remains invisible while still enabling the CSS state.

<div class="field-container">
  <input type="text" id="username" placeholder=" " />
  <label for="username">Username</label>
</div>

Implementing the CSS Logic

The implementation relies on styling the label relative to the input's :placeholder-shown state and overriding it during :focus or when the input contains a value.

  1. Default (Floating) State: Style the label in its small, elevated position by default, or define it via transitions.
  2. Resting (Placeholder) State: When input:placeholder-shown is active and not focused, transform the label downward and scale it up so it resembles standard placeholder text.
  3. Active/Filled State: When the input receives :focus or matches :not(:placeholder-shown), return the label to its floated position.
.field-container {
  position: relative;
}

/* Position the label inside the container */
.field-container label {
  position: absolute;
  top: 0.5rem;
  left: 0.75rem;
  transition: transform 0.2s ease, font-size 0.2s ease, color 0.2s ease;
  pointer-events: none;
  font-size: 0.75rem;
  color: #666;
}

/* Rest the label inside the field when empty and unfocused */
.field-container input:placeholder-shown:not(:focus) + label {
  transform: translateY(0.5rem);
  font-size: 1rem;
  color: #999;
}

/* Float the label on focus */
.field-container input:focus + label {
  transform: translateY(0);
  font-size: 0.75rem;
  color: #0066cc;
}

Advantages of Using :placeholder-shown

Using :placeholder-shown eliminates DOM mutations, event listeners, and layout thrashing associated with JavaScript-driven label states. It functions smoothly with browser autofill features, works reliably across all modern browsers, and ensures form interactions remain responsive even on low-powered devices.