FormsFree

Floating Label

The label sitting inside the field rises onto its top border as soon as you click or a value is present, even one filled by autocomplete. Pure CSS, no script.

CSSFocusTransform

Updated

Hover or click the scene to interact

This effect is free — the Forms category contains 20 effects total, including 2 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

Basket✓—Delivery—Payment

Delivery address

① "Delivery" step of a checkout — Malvoisy shop

WhenThe customer taps "Use my saved address": street, postcode and city fill in at once and all three labels rise together, although no field received focus.
WhyThis is where a label inside the field usually breaks: a value arrives without a click and the text would stay on top of it. Here :placeholder-shown is enough to lift it, exactly as with the browser's autofill.
SettingsA <form> root used as the card (default rgb(26, 26, 36) background kept, so the notch matches without touching --flt-surface), --flt-width: 100%, two-column grid. The scene button writes the three values by script.
TirvanneWeek 46 · Design teamClick a free slot
Mon 13Tue 14Wed 15Thu 16Fri 17 9 amStand-upClient workshopStand-up 10 amClient workshop 11 amPortfolio review1:1 Léa 12 pm

New event

② "New event" bubble in a team calendar — Tirvanne

WhenClicking a free slot opens a small bubble next to it; the "Event title" field gets focus, its label rises and typing can start with no extra gesture.
WhyA 250 px bubble has no room for a label above the field. The text moving into the border keeps the hint readable while typing without making the bubble taller.
SettingsThe bubble is the root itself (width: 250px, default background), --flt-width: 100%. Focus comes from focus({ preventScroll: true }) when the slot is clicked; "Create" adds the event to the grid.
VENDRAQUESendPickup pointsBusiness

Where is your parcel?

The number is in your shipping email or on the receipt.

③ "Tracking number" field in a courier's home banner — Vendraque

WhenThe first thing visitors see: a large heading and a field spanning the whole banner, followed by the Track button. The parcel status appears under the field after submitting.
WhyOne very wide field for one action: the label inside the frame is enough, adds no line above and stays visible when the visitor comes back to fix a digit.
Settings--flt-width: 100% in a flex row with the button; <form> root with a 14 px radius added by the scene. The field uses autocomplete="off" and spellcheck="false", which suits a tracking number.

How it works

Everything rests on a <label class="flt-field"> wrapping the .flt-input field and then the .flt-label text. Wrapping ties the label to the field without an id: you can drop as many copies as you like on one page, each keeps its own label, and clicking the text puts the caret in the right field.

At rest, .flt-label is vertically centred inside the frame (top: 50%, then translateY(-50%)). Two selectors lift it: .flt-input:focus + .flt-label and .flt-input:not(:placeholder-shown) + .flt-label. The second one does the real work: the field carries an invisible placeholder=" " (a single space), and :placeholder-shown stops matching as soon as a value exists, whether typed, pasted, set by a script or by the browser's autofill.

Once raised, the label drops to 0.75rem, takes the accent colour (#818cf8 by default for the text) and gets a background equal to --flt-surface (default #1a1a24, the demo frame colour): that background hides the border line behind the text, which creates the notch. The frame turns indigo too and, during focus only, a 3 px rgba(99, 102, 241, .25) ring tells the active field apart from a merely filled one. Both moves last 0.3 s.

The .fx-0281-root root is only a presentation frame: dark background, at least 250 px tall, field centred. In your own form you can drop it and keep just the <label class="flt-field">.

Accessibility

  • Field name: the .flt-label text is the field's real label (implicit association through the wrapping <label>). Screen readers announce "Your email, edit text" even once the text has risen.
  • Real form field: the supplied field is type="email" with name="email" and autocomplete="email". Inside a <form> it is submitted, validated by the browser (address format) and offered to autofill; on phones the email keyboard opens.
  • Visible focus: the indigo frame alone would not single out the active field when several are filled; the 3 px ring shows on focus only.
  • Reduced motion: with prefers-reduced-motion: reduce, transitions are removed. The label lands straight at the top, nothing is hidden.
  • Contrast: resting label #909098 on the field background (5% white over #1a1a24): 4.7 to 4.8:1; raised label #818cf8 on #1a1a24: 5.8:1. Measured in Chromium, Firefox and WebKit, at rest, focused and filled. If you set your own --flt-accent, keep at least 4.5:1 against --flt-surface: the variable also colours the raised text.

Browser compatibility

Only needs :placeholder-shown (Chrome 47, Firefox 51, Safari 9), CSS variables (Chrome 49, Firefox 31, Safari 9.1) and transitions. Checked in Chromium, Firefox and WebKit (Playwright): two independent copies in one form, click, Tab, touch, value set by script, data submission, 390 px width, reduced motion.

Chrome 49+✓ Full (measured)
Firefox 51+✓ Full (measured)
Safari 9.1+✓ Full (WebKit engine measured)
Edge 79+✓ Full (Chromium engine)
Mobile iOS 9.3+✓ Full
Android Chrome 49+✓ Full (Chromium engine)

A browser that ignores :placeholder-shown drops the label back inside as soon as the field loses focus, on top of the typed value: that only concerns browsers more than eight years old. Without CSS variables, the notch loses its background colour.

The code

Copy the two blocks (HTML and CSS) into your page. No JavaScript, no dependencies.

HTML
<div class="fx-0281-root">
  <label class="flt-field">
    <input class="flt-input" type="email" name="email" autocomplete="email" placeholder=" ">
    <span class="flt-label">Your email</span>
  </label>
</div>
CSS
.fx-0281-root,
.fx-0281-root *,
.fx-0281-root *::before,
.fx-0281-root *::after {
  box-sizing: border-box
}

.fx-0281-root {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 250px;
  padding: 24px 16px;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.flt-field {
  position: relative;
  display: block;
  width: var(--flt-width, 280px);
  max-width: 100%;
}

.flt-input {
  width: 100%;
  padding: 16px 12px 8px;
  font-size: 1rem;
  font-family: inherit;
  background: rgba(255, 255, 255, 0.05);
  border: 2px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  color: white;
  outline: none;
  transition: all 0.3s ease;
}

.flt-label {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--flt-muted, #909098);
  pointer-events: none;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  background: transparent;
  padding: 0 4px;
}

.flt-input:focus,
.flt-input:not(:placeholder-shown) {
  border-color: var(--flt-accent, #6366f1);
  background: rgba(99, 102, 241, 0.05);
}

.flt-input:focus {
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);
}

.flt-input:focus + .flt-label,
.flt-input:not(:placeholder-shown) + .flt-label {
  top: 0;
  font-size: 0.75rem;
  color: var(--flt-accent, #818cf8);
  background: var(--flt-surface, #1a1a24);
}

@media (prefers-reduced-motion: reduce) {
  .flt-input,
  .flt-label {
    transition: none;
  }
}
JavaScript

Pure CSS — no JavaScript needed.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Width (CSS — --flt-width) 280 px Set --flt-width: 100% on the field or a parent so it fills its column. max-width: 100% keeps it from overflowing on a small screen.
Notch background (CSS — --flt-surface) #1a1a24 Must be the exact colour behind the field: on a white card use #fff, otherwise a dark patch shows under the raised label.
Accent colour (CSS — --flt-accent) #6366f1 (frame) · #818cf8 (raised label) Left unset, the frame is #6366f1 and the raised label uses the lighter indigo #818cf8, readable on a dark background. Once set, the variable colours both. The field's light indigo fill and the focus ring stay at rgba(99, 102, 241, …): change them too for another hue.
Resting label colour (CSS — --flt-muted) #909098 Grey of the text while it sits in the field. Keep at least 4.5:1 contrast with the field background.
Field type and name (HTML) type="email" name="email" autocomplete="email" For a name, phone or address, change the three attributes together (tel / tel, text / street-address…). Keep placeholder=" ": without it the label can no longer tell whether the field is empty.
Duration (CSS — transition) 0.3 s Set twice, on .flt-input and .flt-label. Between 0.15 and 0.4 s the move stays readable; beyond that the label lags behind typing.

FAQ

Does the label stay up when the browser fills the field on its own?

Yes. Autofill sets a value, :placeholder-shown turns false and the label rises, no script needed. Same for a value written by your JavaScript (input.value = …) or restored by the Back button.

Why doesn't the label use a for/id attribute?

An id must be unique on the page: with for="email", a second copy of the field would point at the first one and end up unnamed. A <label> wrapping the field makes the same link without any identifier.

Can I show an input example as well as the label?

Not in the placeholder: it must stay a single space for the selector to work. Put the example in small text under the field linked with aria-describedby; it stays readable while typing.