DEMO
CODE
<div class="demo-stage fli-stage">
<form class="fli-form" onsubmit="return false">
<div class="fli-field">
<input id="fli-name" class="fli-input" type="text" placeholder=" " autocomplete="off">
<label for="fli-name" class="fli-label">お名前</label>
</div>
<div class="fli-field">
<input id="fli-mail" class="fli-input" type="email" placeholder=" " autocomplete="off">
<label for="fli-mail" class="fli-label">メールアドレス</label>
<span class="fli-hint">@ を含む形式で入力してください</span>
</div>
</form>
</div>
.fli-stage { display:flex; align-items:center; justify-content:center; padding:2rem; background:#0B0B10; min-height:280px; }
.fli-form { width:100%; max-width:340px; display:flex; flex-direction:column; gap:1.4rem; }
.fli-field { position:relative; }
.fli-input {
width:100%; box-sizing:border-box; padding:1.35rem .9rem .5rem;
font-size:16px; color:#EDEDED; background:#15151B;
border:1px solid rgba(255,255,255,.14); border-radius:10px; outline:none;
transition: border-color .2s ease, box-shadow .2s ease;
}
.fli-label {
position:absolute; left:.9rem; top:.95rem; color:#8A8A96; font-size:.95rem;
pointer-events:none; transform-origin:left top;
transition: transform .22s cubic-bezier(.22,1,.36,1), color .22s ease;
}
.fli-input:focus { border-color:#E91B89; box-shadow:0 0 0 3px rgba(233,27,137,.22); }
.fli-input:focus + .fli-label,
.fli-input:not(:placeholder-shown) + .fli-label { transform:translateY(-.62rem) scale(.76); color:#E91B89; }
.fli-input:not(:focus):not(:placeholder-shown) + .fli-label { color:#9A9AA6; }
.fli-hint { display:block; margin-top:.35rem; font-size:.72rem; color:#F87171; opacity:0; transition: opacity .2s ease; }
.fli-input[type=email]:not(:placeholder-shown):not(:focus):invalid { border-color:#F87171; }
.fli-input[type=email]:not(:placeholder-shown):not(:focus):invalid ~ .fli-hint { opacity:1; }
@media (prefers-reduced-motion: reduce) {
.fli-label, .fli-input, .fli-hint { transition-duration:.01ms; }
}
AI PROMPT
入力欄のラベルが、フォーカス時と入力済みの時に上へ縮んで移動するフローティングラベルをCSSだけで作って。input に placeholder=" "(半角スペース1つ)を入れ、:placeholder-shown が外れた状態=入力済みと判定するのがJavaScriptを使わない要点。ラベルは input の直後に置き、input:focus + label と input:not(:placeholder-shown) + label の両方で transform: translateY(-.62rem) scale(.76) にする(top や font-size を動かすとレイアウト再計算が走るので transform だけで動かすこと)。transform-origin は left top。input の上余白を広めに取り、縮んだラベルが入力文字と重ならないようにする。font-size は16px以上(iOSのフォーカス時自動ズームを防ぐ)。type=email はフォーカスが外れて入力済みかつ :invalid の時だけ枠を赤くし、ヒント文を表示する。label は for 属性で input と関連付け、アクセシブルネームを必ず持たせること。@media (prefers-reduced-motion: reduce) で transition を無効化する。 Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift
AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する
BOOKS · PR · AMAZON
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
学びを止めない · AMAZONサブスク
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
入力前はプレースホルダーのように欄の中に表示され、フォーカスや入力で欄の上端へ小さく移動するラベルです。Googleのマテリアルデザインで広まり、ラベルと入力例の両方を置く場所がないスマホのフォームで特に使われます。
当サイトの Expanding Search は検索欄そのものが横に伸びる演出で、本ページは入力欄の大きさを変えずにラベルだけを動かす別テーマです。項目名が入力後も見えるため、プレースホルダーだけのフォームで起きる「何の欄だったか分からない」を防げます。
実装の要点は :placeholder-shown による入力済み判定と、transform だけで動かすことの2点です。エラー表示はフォーカス中ではなく離れた時に出すと、入力途中で赤くなって急かされる感覚を避けられます。
なぜ placeholder に半角スペースを入れるのですか?
:placeholder-shown は placeholder が表示されている間だけ一致する疑似クラスです。placeholder が空文字だと一致しないため、見えない半角スペースを入れておき、「一致している=未入力」「一致しない=入力済み」の判定に使います。これでJavaScriptなしで入力済みのラベル位置を保てます。
ラベルの移動に top ではなく transform を使う理由は?
top や font-size を変えるとブラウザはレイアウトを再計算し、入力欄が多いフォームでは動きがカクつく原因になります。transform の translate と scale は合成処理だけで済むため滑らかです。transform-origin を left top にすると、縮んでも左端がそろいます。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。