CSS Floating Labels with :placeholder-shown

Floating labels without JavaScript

Floating labels used to need JavaScript. Listen for input events, toggle a .filled class, style from that class. :placeholder-shown tracks empty state for you. Flip it with :not() and the label floats once there is content.

Old way 8 lines
/* .filled class toggled by JS on every keystroke */.field.filled label,.field input:focus + label   {  translate: 0 -1.4rem;  font-size: .75rem;}// JS: toggle .filled on input input.addEventListener('input', () => {   input.closest('.field').classList.toggle('filled', input.value.length > 0); });
3 lines
input:not(:placeholder-shown) + label,input:focus + label   {  translate: 0 -1.4rem;  font-size: .75rem;}/* no .filled class, no JS */
Widely available Since 2020 97% global usage

This feature is well established and works across many devices and browser versions. It has been available across browsers since 2020.

Safe to use without fallbacks.

47+
51+
9+
79+
type in a field to see the label float up
:not(:placeholder-shown) drives the float, no JS

No input listener

No input event listener, no .filled class, no class toggling on every keystroke.

Browser tracks content

:placeholder-shown matches while the placeholder is visible. Flip it with :not() and you have the filled state for free.

Pairs with :focus

Add :focus to the selector list and the label also floats while the field is focused. One rule, two states.

Lines Saved
8 → 3
No JS filled class
Old Approach
.filled + JS input
Class toggle on keystroke
Modern Approach
:not(:placeholder-shown)
Browser tracks placeholder state

How it works

:placeholder-shown matches an input or textarea whose placeholder is still visible. Empty field, placeholder visible, selector matches. The field needs a placeholder attribute for the pseudo-class to have anything to track. Set it to a single space if you do not want visible placeholder text.

:not(:placeholder-shown) is the filled state. Pair it with an adjacent label using the sibling combinator and you can float the label the moment content appears. Put the input before the label in the HTML so + label works.

The old pattern used JavaScript. Listen for input events, toggle a .filled class on the parent, style from that class. This replaces all of it with one selector. Add :focus to the selector list to float the label on focus too.

ESC