← Back to gallery
CSS

Animated Form Inputs

Three floating-label input animations — Outlined Notch (rounded border that notches around the label as it floats up), Underline Slide (bottom bar thickens + brightens on focus while the label slides), and Glow Border (gradient border that ignites on focus with a soft halo). :placeholder-shown + :focus-within drive the float without JavaScript.

floating-labelform-inputoutlined-notchunderlineglow-borderfocus-withinplaceholder-shown

Floating-label · :placeholder-shown + :focus-within

Animated Form Inputs

Three floating-label input animations — Outlined Notch (rounded border that notches around the label as it floats up), Underline Slide (bottom bar thickens + brightens on focus while the label slides), and Glow Border (gradient border that ignites on focus with a soft halo). Stage cards auto-cycle the focus state every 2.6s so the float motion plays without input; inspector preview is fully interactive.

Border notches around the floating label

Outlined Notch

An outlined input with a single rounded-rectangle border. On focus or when the field has a value, the label floats up and the top edge of the border notches around it (legend trick). Active accent stains the border + label.

  • notched border
  • legend trick
  • :focus-within + :placeholder-shown

Bottom bar grows on focus

Underline Slide

An underline input with no outer border. A thin baseline sits at the bottom; on focus an accent bar grows across it from 0 → 100% width. The label translates Y up + scales 0.78 + tints to the accent. Minimal, dense form rhythm.

  • underline grow
  • minimalist
  • left-anchored

Gradient border ignites on focus

Glow Border

An input where the border is a soft multi-stop gradient (cyan → magenta → amber) that brightens on focus. The whole field gains a coloured halo via box-shadow, the label floats up, and the gradient saturates from a desaturated rest state.

  • gradient border
  • box-shadow halo
  • saturation kick

Floating-label inspector

Outlined Notch

click input
  • notched border
  • legend trick
  • :focus-within + :placeholder-shown

An outlined input with a single rounded-rectangle border. On focus or when the field has a value, the label floats up and the top edge of the border notches around it (legend trick). Active accent stains the border + label.

Helped you ship something? 🐟 Send my cat a churu

/* Outlined: a single rounded-rectangle border with a notched gap around the floating label. */
.fl-field {
  position: relative;
  padding-top: 6px;
}

.fl-input {
  width: 100%;
  padding: 14px 14px 12px;
  border: 1.5px solid rgba(248, 250, 252, 0.18);
  border-radius: 10px;
  background: transparent;
  color: inherit;
  outline: none;
  transition: border-color 0.22s ease;
}

.fl-input:focus { border-color: #7dd3fc; }

.fl-label {
  position: absolute;
  top: 16px;
  left: 12px;
  padding: 0 4px;
  background: var(--page-bg);   /* notch trick — match the page background */
  pointer-events: none;
  transition: transform 0.22s ease, color 0.22s ease;
}

/* Float when the field has focus OR a value (use :placeholder-shown
   inverted so a placeholder=" " input always trips the rule). */
.fl-input:focus + .fl-label,
.fl-input:not(:placeholder-shown) + .fl-label {
  transform: translateY(-22px) scale(0.85);
  color: #7dd3fc;
}

How to make this

An animated form input keeps a real label, uses a blank placeholder only for :placeholder-shown state detection, and moves decorative focus bars with transform.

html
1<label class="floating-field">  <input class="floating-field__input"3    type="email" placeholder=" " autocomplete="email" />  <span class="floating-field__label">Email address</span>  <span class="floating-field__bar" aria-hidden="true"></span></label> <style>.floating-field {  position: relative;  display: block;  width: min(100%, 22rem);  padding-block-start: 1.15rem;  color: #e2e8f0;  font: 600 .92rem/1.25 ui-sans-serif, system-ui;}.floating-field__input {  width: 100%;  border: 0;  border-block-end: 1px solid rgba(226, 232, 240, .3);  padding: .7rem 0 .55rem;  color: #f8fafc;  background: transparent;  font: inherit;  outline: none;26}.floating-field__input::placeholder { color: transparent; }28.floating-field__label {  position: absolute;  inset-inline-start: 0;  inset-block-start: 1.85rem;  color: rgba(226, 232, 240, .64);  pointer-events: none;  transform-origin: left center;  transition: transform .22s ease, color .22s ease;}.floating-field__bar {  position: absolute;  inset-inline: 0;  inset-block-end: 0;  block-size: 2px;  background: #7dd3fc;43  transform: scaleX(0);  transform-origin: left center;  transition: transform .24s cubic-bezier(.2,.8,.2,1);}47.floating-field:focus-within .floating-field__bar { transform: scaleX(1); }.floating-field:focus-within .floating-field__label,.floating-field__input:not(:placeholder-shown) + .floating-field__label {  color: #7dd3fc;  transform: translateY(-1.35rem) scale(.82);}53.floating-field__input:focus-visible {  outline: 2px solid #7dd3fc;  outline-offset: .35rem;}@media (prefers-reduced-motion: reduce) {  .floating-field__label,  .floating-field__bar { transition: none; }}</style>

Annotated snippet

  1. Line 1The label wraps the input, visible label text, and decorative bar. That keeps click targeting simple while preserving a real field name.
    PitfallCan I use placeholder text as the label?

    No. Placeholder text disappears or competes with typed content, and it is not a reliable accessible name. Use a real label, then use placeholder=" " only as a CSS state hook if you need :placeholder-shown.

  2. Line 3placeholder=" " is only a CSS state hook. The visible label is still the accessible field name, so the input is not dependent on placeholder text.
    PitfallCan I use placeholder text as the label?

    No. Placeholder text disappears or competes with typed content, and it is not a reliable accessible name. Use a real label, then use placeholder=" " only as a CSS state hook if you need :placeholder-shown.

  3. Line 26The placeholder is hidden because it is not instructional copy. If the field needs help text, place it outside the input and connect it with aria-describedby.
  4. Line 28The floating label is positioned above the input, then moved with transform. It never disappears, so users do not lose the field name after typing.

    Both inputs are blurred and contain "hi@studio.io". The before rule only floats the label on :focus-within, so when focus leaves the input the label drops back over the typed value. The after rule adds input:not(:placeholder-shown), so the label stays floated whenever there is content — typed text is never covered.

    PitfallWhy does my floating label drop over typed text after blur?

    The selector only handles focus. Add a value state such as input:not(:placeholder-shown) or a data-filled attribute so the label remains floated whenever the field contains content.

  5. Line 43The active underline grows with scaleX from a fixed full-width bar. That avoids changing inline size during focus.

    Both bars finish as a 240px solid line. Animating width: 0 → 100% pushes layout (the bar literally grows from a zero-width box). transform: scaleX(0 → 1) keeps the box at full width and only scales the painted result on the compositor.

    PitfallShould underline focus bars animate width or transform?

    Prefer transform: scaleX() on a full-width bar. It is easier to compose, avoids layout-size changes, and keeps the start and end alignment stable.

  6. Line 47The label floats on focus or when the input has a value. The second selector keeps the label up after blur, which prevents it from overlapping typed text.
    PitfallWhy does my floating label drop over typed text after blur?

    The selector only handles focus. Add a value state such as input:not(:placeholder-shown) or a data-filled attribute so the label remains floated whenever the field contains content.

  7. Line 53A separate focus-visible outline is still needed. A moving underline is not always enough contrast for keyboard focus, especially in dense forms.
    PitfallAre animated form inputs safe for accessibility?

    Yes if the label remains visible, the input has a clear focus-visible style, and errors or hints are connected with aria-describedby. The animation should reinforce state, not carry the field name alone.

Other pitfalls

Which browsers support :placeholder-shown?
:placeholder-shown is supported in current Chromium, Firefox, and Safari. If you must support older environments, set a data-filled class from JavaScript and reuse the same floating-label styles.
Advanced

Layer :user-invalid validation onto the same sibling cascade

Both cells autoplay the same 6s cycle: empty → focus-in (bar scales) → type "not-an-email" → blur. BEFORE shows the base recipe behavior — after blur the bar drops back to scaleX(0), the floated label stays cyan, the field looks ordinary; nothing tells the user this value will fail submission. AFTER extends the same sibling cascade with :user-invalid (or :invalid:not(:placeholder-shown) as a fallback): right at blur the bar reappears in red, the label flushes red, and the error message slides in via max-height. Same focus/type/blur beats — the cascade just reaches one more state.

View explanation and full code39 lines

The base recipe handles the active-typing UX (label floats on :focus-within, bar lights up via scaleX) but ignores validation entirely — a blurred field with a bad email and a blurred field with a good email look identical. Drop a single error span after the bar and extend the same sibling cascade with :user-invalid rules: the bar goes red, the label flushes red, and the error message slides into view via a max-height transition. Same selector strategy (state pseudo-class + general sibling combinator), same transition mechanism — just adding the validation states the browser already computes. The :user-invalid pseudo only fires after the user has actually touched the field, so unfilled-but-required inputs do not yell at first paint.

Append these rules inside the <style> block from the base snippet above.

css
/* Advanced: :user-invalid cascade with sliding error reveal — extends   the base recipe. Adds a single .floating-field__error span after the   bar (id matches aria-describedby on the input). The same sibling   cascade pattern the base already uses for :focus-within and   :placeholder-shown now reaches the error span, the bar, and the   label whenever the field has been touched and fails browser   validation. max-height transitions the error in; bar + label color   rules sync the rest of the cascade to the new state.    :user-invalid (Selectors-4) fires only after first blur with a bad   value — :invalid:not(:placeholder-shown) is the older fallback that   approximates the same intent for browsers without :user-invalid. */.floating-field__error {  display: block;  overflow: hidden;  max-height: 0;  margin-top: 0;  color: #f87171;  font-size: .78rem;  line-height: 1.3;  transition: max-height .26s ease, margin-top .26s ease;}.floating-field__input:user-invalid ~ .floating-field__error,.floating-field__input:invalid:not(:placeholder-shown) ~ .floating-field__error {  max-height: 2.6em;  margin-top: .45rem;}.floating-field__input:user-invalid ~ .floating-field__bar,.floating-field__input:invalid:not(:placeholder-shown) ~ .floating-field__bar {  background: #f87171;  transform: scaleX(1);}.floating-field__input:user-invalid ~ .floating-field__label,.floating-field__input:invalid:not(:placeholder-shown) ~ .floating-field__label {  color: #f87171;}@media (prefers-reduced-motion: reduce) {  .floating-field__error { transition: none; }}

Notes

Overview

Floating-label inputs animate the placeholder label up and out of the input box on focus or when the field has a value. The pattern uses pure CSS: :placeholder-shown + :focus-within on a wrapper drive the label transform — no JS state machine. Three variants ship: outlined notch border, bottom bar slide, and gradient halo ignite.

When to use it

Reach for floating labels on signup / login forms, settings screens, and any form where label-above-input would feel verbose. Skip them for accessibility-critical compact forms on mobile — the empty-state placeholder is hard to scan when a screen reader user is filling rapidly. Skip them for inputs that take pre-formatted content (credit card, phone); a persistent label above is more forgiving.

How it works

The wrapper element holds both the input and its <label>. The input is given a placeholder=" " (a single space) so :placeholder-shown evaluates while the input is empty — this is the “is empty” signal. The label sits absolutely positioned over the input. Two state branches: .wrap:focus-within label and .wrap input:not(:placeholder-shown) + label both apply the “floated” transform (translate up, shrink) so the label lifts on focus and stays lifted once the user has typed. The notched variant adds a clip-path or background gap to cut the border where the floated label crosses it.

Production gotchas

Browser autofill commonly skips firing the input event, leaving the field with a value but :placeholder-shown still matching for a frame — the label flickers down then back up. Listen for animationstart on a hidden -webkit-autofill keyframe to detect autofill in Chromium, or fall back to a JS check on mount. Number inputs in Safari ignore single-space placeholders; use a zero-width space or attach the “empty” signal via a wrapper class toggled on input events. Right-to-left languages need transform-origin: right on the label or the float anchor flips wrong.

Accessibility

The label must be a real <label for="..."> bound to the input’s ID — floating it visually does not change its accessible name. Screen readers announce the label normally regardless of visual position. Under prefers-reduced-motion: reduce drop the transition duration to zero so the label snaps to its floated position. Verify focus ring contrast on the input outline once the label floats out of the way.

References

Implementation depth

Animated inputs need the native label relationship first. Floating labels, notches, and underline motion should decorate a real label and input pair, not replace placeholder text as the only instruction.

Autofill and validation states are common breakpoints. Test placeholder-shown, prefilled values, error messages, and high-contrast focus so the label never overlaps typed text or disappears behind the animation.