CSS Float Label

Bulletproof classless CSS-only implementation of Float Label pattern with automatic fallback for ANY non-supporting browser.

/anydigital/float-label-css
from /anydigital/blades

Table of Contents

Demo

Drop-in support for Pico's /docs/forms :

Demo form

More examples:


Killer features


Install

Via CDN:

Just Float Label CSS:

<link href="https://cdn.jsdelivr.net/npm/@anydigital/blades@^0.27.0-beta/assets/float-label.min.css" rel="stylesheet" />

Or with all CSS Blades included:

<link href="https://cdn.jsdelivr.net/npm/@anydigital/blades@^0.27.0-beta/assets/blades.min.css" rel="stylesheet" />

Via npm:

npm install @anydigital/blades

Then import just Float Label CSS:

@import "@anydigital/float-label";

Or with all CSS Blades included:

@import "@anydigital/blades";

Usage

Simply place <input> inside <label> to enable float label:

<label>
  <span>Email</span>
  <input type="email" placeholder="email@example.com" />
</label>

NOTE: W3C allows this!


Alternatively wrap <label> and <input> inside .has-float-label:

<div class="has-float-label">
  <label for="email">Email</label>
  <input id="email" type="email" placeholder="email@example.com" />
</div>

NOTE: for="..." attribute is required on <label> in this case


Credits


Browser support

Any browser with /css-placeholder-shown, /css-has and /css-nesting.

All non-supporting browsers should gracefully fall-back to the default layout.

=> Can be used in ANY browser as is!


Featured by:


How it works

First, we target either:

  1. <label> which :has inner form inputs (classless approach)
  2. or explicit .has-float-label class (alternative approach)
label:has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select),
.has-float-label {
  display: block;
  position: relative;

Then, we define the default/fallback state (when the float label should be minimized):

  > span,
  label {
    position: absolute;
    left: 0;
    top: 0;
    cursor: text;
    font-size: 75%;
  }

Finally, we detect if placeholder is shown, but not in focus. That means we can safely hide it, and enlarge the float label instead:

  *:placeholder-shown:not(:focus)::placeholder {
    opacity: 0;
  }
  &:has(*:placeholder-shown:not(:focus)) {
    > span,
    label {
      font-size: inherit;
      opacity: 50%;
    }
  }
}

The :has(*:placeholder-shown:not(:focus)) trick allows this input state information to propagate to the parent level. This enables modern CSS to target inner float label (<span> or <label>) regardless of its position relative to the input field.

Historically, this was not possible: the float label had to be placed after the input field to be targeted using the input:focus + label selector.