Floating Label Input

The label sits inside the empty field like a placeholder and floats up to the border once you focus or type.

  • CSS
  • Forms & controls
  • Click
  • Added

AI prompt

Build a React text input with a floating label using only CSS. Put the <input> and its <label> in a relative wrapper; give the input placeholder=" " (a single space) so :placeholder-shown tells you when it is empty. When empty and unfocused, the label sits vertically centred inside the field at full size. On :focus or when not :placeholder-shown, move the label up with transform: translateY(-145%) scale(0.8), transform-origin left, over 200ms with cubic-bezier(0.16, 1, 0.3, 1), and give it a background matching the field so it cuts through the border. Change the border and label colour to the accent on focus. The label stays a real <label for>. Under prefers-reduced-motion, move the label without the transition. Label text: "Email address".

Source

Demo.tsx
'use client'

import { useId } from 'react'
import s from './demo.module.css'

export default function FloatingLabel({ labels }: { labels: Record<string, string> }) {
  const id = useId()
  return (
    <div className={s.field}>
      <input id={id} className={s.input} type="email" placeholder=" " autoComplete="off" />
      <label htmlFor={id} className={s.label}>
        {labels.label}
      </label>
    </div>
  )
}
demo.module.css
.field { position: relative; width: min(280px, 100%); }

.input {
  width: 100%;
  height: 54px;
  padding: 0 16px;
  border: 1px solid var(--stage-rule);
  border-radius: 12px;
  outline: none;
  background: var(--stage-bg);
  color: var(--stage-ink);
  font: 400 var(--text-base) / 1 var(--font-body);
  transition: border-color 200ms var(--ease-out);
}

.input:hover { border-color: var(--stage-muted); }
.input:focus { border-color: var(--stage-accent); }
.input:focus-visible { box-shadow: 0 0 0 3px var(--color-focus); }

.label {
  position: absolute;
  top: 50%;
  left: 12px;
  padding: 0 4px;
  background: var(--stage-bg);
  color: var(--stage-muted);
  font: 400 var(--text-base) / 1 var(--font-body);
  transform: translateY(-50%);
  transform-origin: left;
  pointer-events: none;
  transition: transform 200ms var(--ease-out), color 200ms var(--ease-out);
}

.input:focus + .label,
.input:not(:placeholder-shown) + .label {
  transform: translateY(-145%) scale(0.8);
}

.input:focus + .label { color: var(--stage-accent); }

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

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.