GODRICH175

9 Form Validation CSS Animations You Can Paste

Form validation CSS animations are small signals — color, icon, glow — that show a field is right or wrong, no shaking required.

Auto-plays · click a tile to jump to its section · all nine in one zip

The order of this css input validation set follows what a person actually does while filling out a form, not popularity. A real-time message while typing (01) leads into a nickname-availability swap (02), a glow that rises once a field passes (03), and a save badge (04); then an error that refuses to go away (05), a status icon mid-check (06), a password meter (07), a submit button's full journey (08), and finally what's left once someone turns motion down (09).

01Real-time inline error message

What you see in css form validation

Type a badly formatted email and the message line reserved underneath the field slides down slightly, then fades back out.

opacitytranslateY2s 루프

Where to look in the code

The keyframe only touches opacity and translateY, so the input's own height never shifts while the message appears.

.fv1__msg {
  opacity: 0;
  transform: translateY(-6px);
}
@keyframes fv1-in {
  0%, 6% { opacity: 0; transform: translateY(-6px); }
  28%, 78% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-6px); }
}
.fv1.is-demo .fv1__msg { animation: fv1-in $duration $easing infinite; }

02Label color crossfade

What happens on screen

The label above a nickname field swaps from a gray "Nickname" to a mint "This nickname is available." Nothing transitions color directly — two labels sit stacked in the same spot and hand off through opacity.

opacity 크로스페이드absolute 겹침2s 루프

How to read the code

@keyframes fv2-ok {
  0%, 20% { opacity: 0; }
  40%, 90% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes fv2-neutral {
  0%, 20% { opacity: .6; }
  40%, 90% { opacity: 0; }
  100% { opacity: .6; }
}
.fv2.is-demo .fv2__label--ok { animation: fv2-ok $duration $easing infinite; }
.fv2.is-demo .fv2__label--neutral { animation: fv2-neutral $duration $easing infinite; }

03Field validation glow ring

What moves

Once a field passes validation, a soft mint ring rises behind it.

box-shadow 레이어scale 펄스opacity

What to change

A box-shadow layer stacked behind the input combines opacity 0→1 with a scale(.94→1.04) swell to make the glow feel like it's inflating rather than snapping on.

.fv3__glow {
  position: absolute;
  inset: -6px;
  border-radius: $r-control;
  box-shadow: 0 0 0 4px rgba(76, 212, 166, .55);
  opacity: 0;
  transform: scale(.94);
}
@keyframes fv3-glow {
  0%, 15% { opacity: 0; transform: scale(.94); }
  45% { opacity: 1; transform: scale(1.04); }
  75%, 100% { opacity: 0; transform: scale(.94); }
}
.fv3.is-demo .fv3__glow { animation: fv3-glow $duration $easing infinite; }

04Success badge bounce check

What you see

A circular badge bounces into place after a save, and the checkmark inside draws itself.

scale 바운스stroke-dashoffsetopacity

Where to look in the code

The badge scales .4→1.12→1, while the check is one SVG polyline whose stroke-dashoffset counts down from 24 to 0.

@keyframes fv4-badge {
  0%, 10% { opacity: 0; transform: scale(.4); }
  35% { opacity: 1; transform: scale(1.12); }
  50% { transform: scale(1); }
  85%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes fv4-draw {
  0%, 30% { stroke-dashoffset: 24; }
  55%, 100% { stroke-dashoffset: 0; }
}
.fv4__path { stroke-dasharray: 24; stroke-dashoffset: 24; }
.fv4.is-demo .fv4__badge { animation: fv4-badge $duration $easing infinite; }
.fv4.is-demo .fv4__path { animation: fv4-draw $duration linear infinite; }

05Error field breathing ring

What happens on screen

Unlike a shake, this field never moves at all.

box-shadow 브리딩opacityscale

How to read the code

A soft coral ring around it breathes between opacity and scale(.97↔1.03) on a slow loop, so an error that hasn't been fixed keeps quietly reminding the user.

.fv5__ring {
  position: absolute;
  inset: -5px;
  border-radius: $r-control;
  box-shadow: 0 0 0 3px $error-soft;
  opacity: 0;
  transform: scale(.97);
}
@keyframes fv5-breathe {
  0%, 100% { opacity: 0; transform: scale(.97); }
  50% { opacity: 1; transform: scale(1.03); }
}
.fv5.is-demo .fv5__ring { animation: fv5-breathe $duration $easing infinite; }

06Status icon spinner-to-check/cross morph

What moves

While a username check is in flight, the icon on the right of the field spins, then hands off to a mint check, then to a coral cross.

rotateopacity 크로스페이드svg 3겹

What to change

Three icons sit stacked in the same spot, crossfading via opacity, with rotate layered only on the spinner.

@keyframes fv6-spinner-show { 0%, 32% { opacity: 1; } 38%, 100% { opacity: 0; } }
@keyframes fv6-check-show { 0%, 32% { opacity: 0; } 40%, 62% { opacity: 1; } 70%, 100% { opacity: 0; } }
@keyframes fv6-cross-show { 0%, 62% { opacity: 0; } 70%, 96% { opacity: 1; } 100% { opacity: 0; } }
.fv6.is-demo .fv6__spinner {
  animation: fv6-spin $dur-base linear infinite,
             fv6-spinner-show $duration $easing infinite;
}
.fv6.is-demo .fv6__check { animation: fv6-check-show $duration $easing infinite; }
.fv6.is-demo .fv6__cross { animation: fv6-cross-show $duration $easing infinite; }

073-tier password strength fill

What you see

Three bars — weak, mid, strong — sit stacked on one track.

scaleXopacity 단계 교체transform-origin:left

Where to look in the code

Each grows via scaleX (transform-origin: left) and then hands the track off to the next tier through opacity, so the meter reads as bars swapping rather than one bar changing color.

.fv7__fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 100%;
  transform-origin: left center;
  transform: scaleX(0);
  opacity: 0;
}
@keyframes fv7-strong {
  0%, 68% { transform: scaleX(.64); opacity: 0; }
  78%, 96% { transform: scaleX(1); opacity: 1; }
  100% { opacity: 0; transform: scaleX(0); }
}
.fv7.is-demo .fv7__fill--strong { animation: fv7-strong $duration $easing infinite; }

08Submit button loading-to-success morph

What happens on screen

One button covers a full submit journey in a single loop: a press (scale(1→.96→1)), a spinner rotating where the label just faded out, then a crossfade into a mint check.

scalerotateopacity 크로스페이드

How to read the code

The only properties animated here are opacity, transform: scale, and transform: rotate.

@keyframes fv8-press { 0%, 8% { transform: scale(1); } 14% { transform: scale(.96); } 22%, 100% { transform: scale(1); } }
@keyframes fv8-check-show {
  0%, 66% { opacity: 0; transform: scale(.5); }
  74% { opacity: 1; transform: scale(1.15); }
  86%, 100% { opacity: 1; transform: scale(1); }
}
.fv8.is-demo { animation: fv8-press $duration $easing infinite; }
.fv8.is-demo .fv8__check { animation: fv8-check-show $duration $easing infinite; }

09Static fallback for reduced motion

What moves

Normally a status icon pops in via scale(.5→1.15→1) and opacity, then fades out on a loop.

scale+opacity 팝prefers-reduced-motion정지 상태 고정

What to change

With reduced motion on, the animation turns off entirely and the check and its message just sit fixed at the final state.

@keyframes fv9-pop {
  0%, 10% { opacity: 0; transform: scale(.5); }
  40% { opacity: 1; transform: scale(1.15); }
  55%, 85% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(.5); }
}
.fv9.is-demo .fv9__status { animation: fv9-pop $duration $easing infinite; }
@media (prefers-reduced-motion: reduce) {
  .fv9.is-demo .fv9__status { animation: none !important; opacity: 1; transform: scale(1); }
}

Where this breaks — a crossfade trap

This one actually bit us while building 02. Crossfading a neutral label and a success label with animation-direction: reverse on one shared keyframe looked like a shortcut — mirror the same timing, save a few lines. But reverse only flips progress (0–100%), and unless the keyframe is perfectly symmetric, both labels land on opacity: 1 near the middle of the loop at the same time, so the two lines of text sit visibly on top of each other. It only showed up once we opened the rendered frames one by one. The fix was to drop reverse and write a second @keyframes (fv2-neutral) whose opacity values are the literal inverse of the first at every stop. The password for the vanilla and React source of all nine is mk7s3ujk, and comparing those two blocks side by side in the 02 folder makes the difference obvious.

Accessibility

All nine turn their animation off under prefers-reduced-motion: reduce. But 03, 04, 07, and 08 use a shape change as the actual information, so switching off the animation without a fallback would hide the result itself — those items jump straight to their final frame (glow already on, check already drawn, bar already filled) instead of just freezing mid-motion. Per MDN's prefers-reduced-motion reference, the media feature reads straight from the OS setting, so validation state has to survive even when motion doesn't. Sticking to transform and opacity is also why these stay smooth on lower-end phones — web.dev's high-performance animations guide covers why those two properties skip layout entirely.

For the shake-based version of the same idea, see 9 CSS shake animations for form errors; custom checkbox and radio validation states live in 9 custom checkbox and radio animations.

FAQ

Why does this form validation ui crossfade opacity instead of transitioning the color directly?

Transitioning color or border-color makes the browser compute intermediate colors every frame, and on a page where colors come from CSS variables (dark mode, for instance) those in-between values can look wrong. Stacking two elements that already hold their final colors and only moving opacity skips that math entirely — each color is correct from frame one.

Can I draw the checkmark with two rotated bars instead of stroke-dashoffset?

You can, but if the two bars don't start from the exact same corner, the check reads as one diagonal line instead of a check — a common bug on this kind of icon. An SVG polyline animated with stroke-dasharray/stroke-dashoffset, like item 04, removes that failure mode completely.

Is it safe to run all nine on one form at once?

It works, but stacking multiple always-on loops — 05's breathing ring next to 03's glow — makes the form feel restless. A good rule: keep state-change animations like 02 and 06, which only move when something changes, but limit continuously looping ones like 05 to at most one per screen.

Enter the archive password

The password is inside this article. You will find it as you read.