Motion Lab / Forms / microinteractions / minimal
// Mechanisme: form-microinteractions-minimal (techniek #19 — float-label + checkmark) // Bij focus: label translate y:-8 + scale 0.85. Bij non-empty input: mini-checkmark fade-in. // Auto-demo: cycle door 3 fields om de 2s, focust + filt programmatically. import gsap from 'https://esm.sh/[email protected]'; const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const fields = document.querySelectorAll('.mini-field'); fields.forEach((f) => { const inp = f.querySelector('input,textarea'); const lbl = f.querySelector('.mini-label'); const chk = f.querySelector('.mini-check'); const sync = () => { const lifted = !!inp.value || document.activeElement === inp; if (reduce) { lbl.style.transform = lifted ? 'translateY(-18px) scale(0.85)' : 'none'; chk.style.opacity = inp.value ? '1' : '0'; return; } gsap.to(lbl, { y: lifted ? -18 : 0, scale: lifted ? 0.85 : 1, duration: 0.3, ease: 'power2.out', transformOrigin: 'left center' }); gsap.to(chk, { autoAlpha: inp.value ? 1 : 0, duration: 0.25 }); }; inp.addEventListener('focus', sync); inp.addEventListener('blur', sync); inp.addEventListener('input', sync); }); const samples = ['Anna de Vries', '[email protected]', 'Graag een offerte voor een nieuwe website.']; let idx = 0; const tick = () => { const inputs = Array.from(document.querySelectorAll('.mini-field input,.mini-field textarea')); if (!inputs.length) return; inputs.forEach((i) => { i.value = ''; i.dispatchEvent(new Event('input')); }); const tgt = inputs[idx % inputs.length]; tgt.focus(); const text = samples[idx % samples.length]; let n = 0; const type = () => { if (n <= text.length) { tgt.value = text.slice(0, n); tgt.dispatchEvent(new Event('input')); n++; setTimeout(type, 35); } }; type(); idx++; }; if (!reduce) { setTimeout(tick, 800); setInterval(tick, 4500); }
<!-- Skeleton: form-microinteractions-minimal -->
<form class="mini-form">
<div class="mini-field">
<input type="text" />
<span class="mini-label">Naam</span>
<svg class="mini-check" viewBox="0 0 24 24" width="14" height="14"><path d="M5 12 L10 17 L19 8" stroke="currentColor" stroke-width="1" fill="none"/></svg>
</div>
<!-- 3 fields -->
<button type="submit">Verstuur</button>
</form> /* Styling: form-microinteractions-minimal */
:root {
--block-paper: #F4F1EB;
--block-ink: #0A0A0A;
--block-rule: rgba(10,10,10,.18);
--block-muted: rgba(10,10,10,.45);
}
.mini-form { background: var(--block-paper); color: var(--block-ink); font-family: 'Inter', system-ui, sans-serif; font-weight: 300; max-width: 480px; display: flex; flex-direction: column; gap: 2.5rem; padding: clamp(3rem,6vw,5rem) 0; }
.mini-form .mini-field { position: relative; padding-top: 1.25rem; }
.mini-form .mini-field input,
.mini-form .mini-field textarea { width: 100%; appearance: none; border: 0; border-bottom: 1px solid var(--block-rule); background: transparent; padding: .6rem 1.5rem .6rem 0; font: 300 1rem/1.5 'Inter', sans-serif; color: var(--block-ink); outline: none; resize: none; }
.mini-form .mini-field input:focus,
.mini-form .mini-field textarea:focus { border-bottom-color: var(--block-ink); }
.mini-form .mini-field textarea { min-height: 4rem; }
.mini-form .mini-label { position: absolute; left: 0; top: 1.85rem; font: 300 1rem/1 'Inter', sans-serif; color: var(--block-muted); pointer-events: none; }
.mini-form .mini-check { position: absolute; right: 0; top: 1.85rem; opacity: 0; visibility: hidden; color: var(--block-ink); }
.mini-form button { align-self: flex-start; appearance: none; background: var(--block-ink); color: var(--block-paper); border: 0; padding: .9rem 2rem; font: 300 .95rem 'Inter', sans-serif; letter-spacing: .02em; cursor: pointer; }