ユーザー登録やアンケートなど、入力項目が多い画面で利用されるマルチステップフォーム(ウィザード形式)のUIコンポーネントです。 ステップごとの進捗状況を視覚的に伝えるプログレスインジケーターと、滑らかなスライドアニメーションを備えています。
ログインに使用する情報を入力してください。
お客様の基本情報をお知らせください。
ring ユーティリティでハイライト強調することで、現在のステータスを直感的に伝えます。emerald)に変化し、同時にラベルも「送信する」に切り替わることで、最終アクションを明確に促します。indigo-600 などの色指定をプロジェクトのテーマカラー(例: blue-600 や rose-500)に一括置換するだけで、簡単に色調をカスタマイズできます。<div class="msf-step-indicator">...</div> および <div class="msf-step-content">...</div> のセットを増減させ、JavaScript内の const steps = [1, 2, 3]; を合わせることで、ステップ数を柔軟に変更可能です。btnNext.addEventListener) 内に、現在のステップごとの入力チェック(必須項目・フォーマットチェック等)を実装し、エラーがなければ currentStep++ するという制御を加えることで、実運用に対応できます。<div class="multi-step-form-wrapper bg-gray-50 dark:bg-zinc-950 flex items-center justify-center p-6 min-h-[600px] font-sans">
<div class="bg-white dark:bg-zinc-900 rounded-2xl shadow-xl w-full max-w-lg p-8 relative overflow-hidden border border-gray-100 dark:border-zinc-800">
<!-- Progress Bar -->
<div class="flex justify-between items-center mb-10 relative">
<div class="absolute left-0 top-1/2 transform -translate-y-1/2 w-full h-1.5 bg-gray-200 dark:bg-zinc-800 z-0 rounded-full"></div>
<div id="msf-progress-bar" class="absolute left-0 top-1/2 transform -translate-y-1/2 h-1.5 bg-indigo-600 z-0 transition-all duration-500 ease-out rounded-full" style="width: 0%;"></div>
<!-- Step 1 -->
<div class="relative z-10 flex flex-col items-center">
<div class="msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-indigo-600 text-white shadow-md border-4 border-white dark:border-zinc-900 ring-4 ring-indigo-100" data-step="1">1</div>
<span class="msf-step-label text-xs font-bold text-indigo-600 mt-2 transition-colors duration-300 tracking-wide uppercase">Account</span>
</div>
<!-- Step 2 -->
<div class="relative z-10 flex flex-col items-center">
<div class="msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-gray-200 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 dark:text-gray-500 border-4 border-white dark:border-zinc-900" data-step="2">2</div>
<span class="msf-step-label text-xs font-medium text-gray-400 dark:text-gray-500 mt-2 transition-colors duration-300 tracking-wide uppercase">Personal</span>
</div>
<!-- Step 3 -->
<div class="relative z-10 flex flex-col items-center">
<div class="msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-gray-200 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 dark:text-gray-500 border-4 border-white dark:border-zinc-900" data-step="3">3</div>
<span class="msf-step-label text-xs font-medium text-gray-400 dark:text-gray-500 mt-2 transition-colors duration-300 tracking-wide uppercase">Review</span>
</div>
</div>
<!-- Form Steps -->
<div class="relative h-[280px] overflow-visible">
<!-- Step 1 Content -->
<div class="msf-step-content absolute w-full transition-all duration-500 transform translate-x-0 opacity-100" data-step="1">
<h2 class="text-2xl font-extrabold text-gray-900 dark:text-white mb-2">アカウント情報</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 dark:text-gray-500 mb-6">ログインに使用する情報を入力してください。</p>
<div class="space-y-4">
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">メールアドレス</label>
<input type="email" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="you@example.com" />
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">パスワード</label>
<input type="password" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="••••••••" />
</div>
</div>
</div>
<!-- Step 2 Content -->
<div class="msf-step-content absolute w-full transition-all duration-500 transform translate-x-12 opacity-0 pointer-events-none" data-step="2">
<h2 class="text-2xl font-extrabold text-gray-900 dark:text-white mb-2">個人情報</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 dark:text-gray-500 mb-6">お客様の基本情報をお知らせください。</p>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">姓</label>
<input type="text" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="山田" />
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">名</label>
<input type="text" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="太郎" />
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">電話番号</label>
<input type="tel" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="090-1234-5678" />
</div>
</div>
</div>
<!-- Step 3 Content -->
<div class="msf-step-content absolute w-full transition-all duration-500 transform translate-x-12 opacity-0 pointer-events-none" data-step="3">
<h2 class="text-2xl font-extrabold text-gray-900 dark:text-white mb-2">入力内容の確認</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 dark:text-gray-500 mb-6">以下の内容で登録します。よろしいですか?</p>
<div class="bg-gray-50 dark:bg-zinc-950/80 backdrop-blur-sm rounded-xl p-5 border border-gray-100 dark:border-zinc-800 shadow-inner space-y-4 text-sm">
<div class="flex justify-between items-center border-b border-gray-200 dark:border-zinc-800 pb-3">
<span class="text-gray-500 dark:text-gray-400 dark:text-gray-500 font-medium">メールアドレス</span>
<span class="font-bold text-gray-800 dark:text-gray-200">you@example.com</span>
</div>
<div class="flex justify-between items-center border-b border-gray-200 dark:border-zinc-800 pb-3">
<span class="text-gray-500 dark:text-gray-400 dark:text-gray-500 font-medium">氏名</span>
<span class="font-bold text-gray-800 dark:text-gray-200">山田 太郎</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500 dark:text-gray-400 dark:text-gray-500 font-medium">電話番号</span>
<span class="font-bold text-gray-800 dark:text-gray-200">090-1234-5678</span>
</div>
</div>
</div>
</div>
<!-- Buttons -->
<div class="flex justify-between mt-8 pt-6 border-t border-gray-100 dark:border-zinc-800 relative z-10">
<button id="msf-btn-prev" class="px-6 py-2.5 border border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl hover:bg-gray-50 dark:bg-zinc-950 hover:text-indigo-600 focus:outline-none focus:ring-2 focus:ring-gray-200 transition-all duration-300 opacity-0 pointer-events-none disabled:opacity-50 flex items-center justify-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
戻る
</button>
<button id="msf-btn-next" class="px-6 py-2.5 bg-indigo-600 text-white font-bold rounded-xl hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-300 ml-auto shadow-[0_4px_14px_0_rgba(79,70,229,0.39)] hover:shadow-[0_6px_20px_rgba(79,70,229,0.23)] hover:-translate-y-0.5 flex items-center justify-center gap-2 active:translate-y-0">
次へ
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</button>
</div>
</div>
</div>
<script>
(function() {
const steps = [1, 2, 3];
let currentStep = 1;
const progressBar = document.getElementById('msf-progress-bar');
const indicators = document.querySelectorAll('.msf-step-indicator');
const labels = document.querySelectorAll('.msf-step-label');
const contents = document.querySelectorAll('.msf-step-content');
const btnPrev = document.getElementById('msf-btn-prev');
const btnNext = document.getElementById('msf-btn-next');
if (!progressBar) return;
function validateStep() {
if (currentStep === steps.length) return true;
const currentContent = document.querySelector('.msf-step-content[data-step="' + currentStep + '"]');
const inputs = currentContent.querySelectorAll('input[required]');
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.reportValidity();
isValid = false;
}
});
return isValid;
}
function updateUI() {
const progressPercentage = ((currentStep - 1) / (steps.length - 1)) * 100;
progressBar.style.width = progressPercentage + '%';
indicators.forEach((ind, index) => {
const stepNum = index + 1;
const label = labels[index];
if (stepNum < currentStep) {
ind.className = "msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-indigo-600 text-white shadow-md border-4 border-white dark:border-zinc-900";
ind.innerHTML = '<svg class="w-5 h-5 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path></svg>';
label.className = "msf-step-label text-xs font-bold text-indigo-600 mt-2 transition-colors duration-300 tracking-wide uppercase";
} else if (stepNum === currentStep) {
ind.className = "msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-indigo-600 text-white shadow-md border-4 border-white dark:border-zinc-900 ring-4 ring-indigo-100 scale-110";
ind.innerHTML = stepNum;
label.className = "msf-step-label text-xs font-bold text-indigo-600 mt-2 transition-colors duration-300 tracking-wide uppercase";
} else {
ind.className = "msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-gray-200 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 dark:text-gray-500 border-4 border-white dark:border-zinc-900";
ind.innerHTML = stepNum;
label.className = "msf-step-label text-xs font-medium text-gray-400 dark:text-gray-500 mt-2 transition-colors duration-300 tracking-wide uppercase";
}
});
contents.forEach((content, index) => {
const stepNum = index + 1;
if (stepNum === currentStep) {
content.className = "msf-step-content absolute w-full transition-all duration-500 transform translate-x-0 opacity-100 delay-100";
} else if (stepNum < currentStep) {
content.className = "msf-step-content absolute w-full transition-all duration-500 transform -translate-x-12 opacity-0 pointer-events-none";
} else {
content.className = "msf-step-content absolute w-full transition-all duration-500 transform translate-x-12 opacity-0 pointer-events-none";
}
});
if (currentStep === 1) {
btnPrev.className = "px-6 py-2.5 border border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl transition-all duration-300 opacity-0 pointer-events-none absolute left-0";
} else {
btnPrev.className = "px-6 py-2.5 border border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl hover:bg-gray-50 dark:bg-zinc-950 hover:text-indigo-600 focus:outline-none focus:ring-2 focus:ring-gray-200 transition-all duration-300 opacity-100 flex items-center justify-center gap-2";
}
if (currentStep === steps.length) {
btnNext.innerHTML = '送信する <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>';
btnNext.className = "px-6 py-2.5 bg-emerald-500 text-white font-bold rounded-xl hover:bg-emerald-600 focus:outline-none focus:ring-2 focus:ring-emerald-400 focus:ring-offset-2 transition-all duration-300 ml-auto shadow-[0_4px_14px_0_rgba(16,185,129,0.39)] hover:shadow-[0_6px_20px_rgba(16,185,129,0.23)] hover:-translate-y-0.5 flex items-center justify-center gap-2 active:translate-y-0";
} else {
btnNext.innerHTML = '次へ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>';
btnNext.className = "px-6 py-2.5 bg-indigo-600 text-white font-bold rounded-xl hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-300 ml-auto shadow-[0_4px_14px_0_rgba(79,70,229,0.39)] hover:shadow-[0_6px_20px_rgba(79,70,229,0.23)] hover:-translate-y-0.5 flex items-center justify-center gap-2 active:translate-y-0";
}
}
btnPrev.addEventListener('click', () => {
if (currentStep > 1) {
currentStep--;
updateUI();
}
});
btnNext.addEventListener('click', () => {
if (!validateStep()) return;
if (currentStep < steps.length) {
currentStep++;
updateUI();
} else {
btnNext.innerHTML = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>送信中...';
setTimeout(() => {
alert('フォームが送信されました!');
currentStep = 1;
updateUI();
}, 1500);
}
});
updateUI();
})();
</script> モダンブラウザ(Chrome, Safari, Firefox, Edge)の最新バージョンで正常に動作します。アニメーションにはCSS TransitionsとTailwindのユーティリティクラスを使用しており、JavaScriptは状態管理(クラスの付け替えと進捗バーの幅計算のみ)を行っているため、軽量かつ高速に動作します。