<div class="container">
  <div class="placeholder">NAME</div>
  <svg viewBox="0 0 320 100" width="320" height="100">
    <path stroke="#000" stroke-width="2" fill="none" />
  </svg>
  <input type="text">
</div>
@import url('https://rsms.me/inter/inter-ui.css');
body {
  align-items: center;
  display: flex;
  font-family: 'Inter UI', sans-serif;
  font-size: 14px;
  height: 100vh;
  justify-content: center;
  margin: 0;
}
.container {
  display: flex;
  flex-direction: column;
  position: relative;
  width: 320px;
}
.placeholder {
  transform-origin: 0 50%;
  transform: translateX(10px);
}
.placeholder.expand {
  animation: MovePlaceholder 220ms both linear;
}
.placeholder.return {
  animation: ReturnPlaceholder 220ms both linear;
}
input {
  background: transparent;
  border: 0;
  font-size: 20px;
  height: 30px;
  outline: none !important;
  position: absolute;
  top: -12px;
}
svg {
  position: relative;
  top: -25px;
}
@keyframes MovePlaceholder {
  0% { transform: translateX(10px) translateY(0) rotate(0) }
  60% { transform: translateX(4px) translateY(-8px) rotate(-18deg) scale(0.92) }
  100% { transform: translateX(0) translateY(-30px) rotate(0deg) scale(0.75) }
}
@keyframes ReturnPlaceholder {
  0% { transform: translateX(0) translateY(-30px) scale(0.75) }
  100% { transform: translateX(10px) translateY(0) }
}
let start = null;
const path = document.querySelector('path');
const bump = 0;
path.setAttribute('d', `m 0,30 c 16.920055,0 8.8823519,-${bump} 30,-${bump} 21.117648,0 13.001352,${bump} 30,${bump} h 260`);
const duration = 600;
const easing = BezierEasing(0.4, 0.0, 0.2, 1);
const ph = document.querySelector('.placeholder');
const input = document.querySelector('input');
input.addEventListener('focus', () => {
  if (input.value.length === 0) {
    ph.classList.remove('return');
    ph.classList.add('expand');
    window.requestAnimationFrame(step);
  }
})
input.addEventListener('blur', () => {
  if (input.value.length === 0) {
    ph.classList.remove('expand');
    ph.classList.add('return');
  }
})

const animate = (inPct) => {
  const pct = easing(inPct);
  const newPct = 1 - 2 * Math.abs(0.5 - pct);
  const newBump = newPct * 25;
  path.setAttribute('d', `m 0,30 c 16.920055,0 8.8823519,-${newBump} 30,-${newBump} 21.117648,0 13.001352,${newBump} 30,${newBump} h 260`);
};

function step(timestamp) {
  if (!start) start = timestamp;
  const progress = (timestamp - start);
  let pct = progress / duration;
  if (pct > 1) pct = 1;
  animate(pct);
  if (progress < duration) {
    window.requestAnimationFrame(step);
  } else {
    start = null;
  }
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/bezier-easing@2.1.0/dist/bezier-easing.min.js