שאלה אחת בכל מסך במקום טופס ארוך ומפחיד. פס התקדמות למעלה, מעבר מונפש בין השלבים, ולידציה לפני מעבר, וסיכום לפני שליחה.
מתי משתמשים: כל טופס שמבקש יותר משלושה שדות: בקשת הצעת מחיר, קביעת תור, שאלון אפיון, חישוב עלות. מעלה השלמות מול טופס אחד ארוך.
הדמו כאן עיצובי-ניטרלי בכוונה. כשהמהלך נכנס לפרויקט, מיובאת רק ההתנהגות: הצבעים, הרדיוסים, הפונטים והצללים יורשים את העיצוב של אותו פרויקט.
הקוד כאן עומד בפני עצמו. כל טוקן עיצוב נושא ברירת מחדל, מחלקות העזר של המאגר מוטמעות בו, ותגי הסקריפט מסודרים לפי הסדר הנכון. פרויקט שמגדיר --accent או --line משלו יורש אותם אוטומטית. הרכיב הזה מסתמך על dir="rtl".
/* מחלקות עזר של המאגר שהרכיב הזה משתמש בהן */
.stage{padding:var(--sec, clamp(120px, 12vw, 260px)) var(--gutter, clamp(24px, 5vw, 96px));position:relative}
.stage.tight{padding-block:clamp(48px,5vw,90px)}
.stage.full{padding-inline:0}
/* B36 · טופס רב-שלבי עם התקדמות */
.ms{max-width:min(620px,92vw);margin-inline:auto;background:var(--card, #fff);border:1px solid var(--line, #e4e4ee);border-radius:20px;padding:clamp(22px,3vw,38px);overflow:hidden}
.ms-top{display:flex;align-items:center;gap:14px;margin-bottom:26px}
.ms-bar{flex:1;height:6px;border-radius:999px;background:var(--line, #e4e4ee);overflow:hidden}
.ms-bar i{display:block;height:100%;width:100%;transform-origin:0 50%;transform:scaleX(0);background:var(--accent, #4a3aff);border-radius:999px;transition:transform .45s cubic-bezier(.2,.6,.2,1)}
html[dir="rtl"] .ms-bar i{transform-origin:100% 50%}
.ms-num{font-size:13px;color:var(--muted, #6a6d85);font-variant-numeric:tabular-nums;white-space:nowrap}
.ms-view{position:relative}
.ms-step{display:none}
.ms-step.on{display:block;animation:msIn .38s cubic-bezier(.2,.6,.2,1)}
.ms-step.back.on{animation:msBack .38s cubic-bezier(.2,.6,.2,1)}
@keyframes msIn{from{opacity:0;transform:translateX(-26px)}to{opacity:1;transform:none}}
@keyframes msBack{from{opacity:0;transform:translateX(26px)}to{opacity:1;transform:none}}
.ms-step h3{margin:0 0 6px;font-size:clamp(20px,2.2vw,28px)}
.ms-step p.sub{margin:0 0 18px;color:var(--muted, #6a6d85);font-size:15px}
.ms-opts{display:grid;gap:10px}
.ms-opt{display:flex;align-items:center;gap:12px;border:1px solid var(--line, #e4e4ee);border-radius:14px;padding:15px 16px;cursor:pointer;
background:var(--bg, #f7f7fa);transition:border-color .22s,background .22s}
.ms-opt:hover{border-color:var(--accent, #4a3aff)}
.ms-opt input{accent-color:var(--accent, #4a3aff);width:18px;height:18px;flex:none}
.ms-opt.sel{border-color:var(--accent, #4a3aff);background:#eceaff}
.ms-field input{width:100%;font:inherit;font-size:16px;padding:15px 16px;border:1px solid var(--line, #e4e4ee);border-radius:14px;background:var(--bg, #f7f7fa);color:var(--ink, #16182b)}
.ms-field input:focus{outline:0;border-color:var(--accent, #4a3aff)}
.ms-err{font-size:13px;color:#c2255c;margin-top:8px;display:none}
.ms-err.on{display:block}
.ms-nav{display:flex;gap:10px;margin-top:24px}
.ms-nav button{flex:1;min-height:50px;border-radius:999px;border:0;font:inherit;font-weight:600;font-size:16px;cursor:pointer}
.ms-back{background:var(--bg, #f7f7fa);border:1px solid var(--line, #e4e4ee)!important;color:var(--ink, #16182b);flex:0 0 auto;padding-inline:22px}
.ms-next{background:var(--accent, #4a3aff);color:var(--accent-ink, #fff)}
.ms-sum{list-style:none;margin:0 0 4px;padding:0;display:grid;gap:8px}
.ms-sum li{display:flex;justify-content:space-between;gap:16px;font-size:15px;border-bottom:1px dashed var(--line, #e4e4ee);padding-bottom:8px}
.ms-sum b{font-weight:600}
.ms-done{text-align:center;padding:10px 0}
.ms-done .ok{width:56px;height:56px;border-radius:50%;background:#2b8a3e;color:#fff;display:grid;place-items:center;font-size:26px;margin:0 auto 14px}
@media (prefers-reduced-motion: reduce){.ms-step.on{animation:none}}
<div class="stage tight"><form class="ms" novalidate>
<div class="ms-top"><div class="ms-bar"><i></i></div><span class="ms-num"></span></div>
<div class="ms-view">
<section class="ms-step on" data-key="סוג הפרויקט">
<h3>מה אתם צריכים?</h3><p class="sub">אפשר לבחור אחד</p>
<div class="ms-opts">
<label class="ms-opt"><input type="radio" name="type" value="אתר תדמית"><span>אתר תדמית</span></label>
<label class="ms-opt"><input type="radio" name="type" value="דף נחיתה"><span>דף נחיתה</span></label>
<label class="ms-opt"><input type="radio" name="type" value="חנות אונליין"><span>חנות אונליין</span></label>
</div><p class="ms-err">צריך לבחור אפשרות אחת</p>
</section>
<section class="ms-step" data-key="תקציב">
<h3>מה טווח התקציב?</h3><p class="sub">זה עוזר להתאים הצעה מדויקת</p>
<div class="ms-opts">
<label class="ms-opt"><input type="radio" name="budget" value="עד 8,000"><span>עד 8,000 ש"ח</span></label>
<label class="ms-opt"><input type="radio" name="budget" value="8,000 עד 15,000"><span>8,000 עד 15,000 ש"ח</span></label>
<label class="ms-opt"><input type="radio" name="budget" value="מעל 15,000"><span>מעל 15,000 ש"ח</span></label>
</div><p class="ms-err">צריך לבחור טווח</p>
</section>
<section class="ms-step" data-key="פרטים">
<h3>איך חוזרים אליכם?</h3><p class="sub">שם וטלפון, וזהו</p>
<div class="ms-opts">
<div class="ms-field"><input type="text" name="name" placeholder="שם מלא" autocomplete="name"></div>
<div class="ms-field"><input type="tel" name="phone" placeholder="טלפון" autocomplete="tel"></div>
</div><p class="ms-err">צריך שם וטלפון תקין</p>
</section>
<section class="ms-step" data-key="סיכום">
<h3>רגע לפני ששולחים</h3><p class="sub">אפשר לחזור ולתקן כל שלב</p>
<ul class="ms-sum"></ul>
</section>
<section class="ms-step ms-done-step">
<div class="ms-done"><div class="ok">✓</div><h3>נשלח</h3><p class="sub">נחזור אליכם היום. הדגמה בלבד, שום דבר לא נשלח.</p></div>
</section>
</div>
<div class="ms-nav"><button type="button" class="ms-back">חזרה</button><button type="button" class="ms-next">המשך</button></div>
</form></div>
(function(){
const form=document.querySelector(".ms");
const steps=[...form.querySelectorAll(".ms-step")];
const bar=form.querySelector(".ms-bar i"),num=form.querySelector(".ms-num");
const back=form.querySelector(".ms-back"),next=form.querySelector(".ms-next");
const sum=form.querySelector(".ms-sum");
const last=steps.length-1; // מסך התודה
const review=last-1; // מסך הסיכום
let i=0;
form.querySelectorAll(".ms-opt input").forEach(inp=>{
inp.addEventListener("change",()=>{
inp.closest(".ms-opts").querySelectorAll(".ms-opt").forEach(o=>o.classList.remove("sel"));
inp.closest(".ms-opt").classList.add("sel");
inp.closest(".ms-step").querySelector(".ms-err").classList.remove("on");
});
});
function valid(n){
const s=steps[n];
const radios=s.querySelectorAll('input[type=radio]');
if(radios.length)return [...radios].some(r=>r.checked);
const fields=[...s.querySelectorAll('input[type=text],input[type=tel]')];
if(fields.length)return fields.every(f=>f.name==="phone"?/^0\d{1,2}-?\d{7}$/.test(f.value.replace(/\s/g,"")):f.value.trim().length>1);
return true;
}
function paintSummary(){
sum.innerHTML="";
steps.slice(0,review).forEach(s=>{
const r=s.querySelector('input[type=radio]:checked');
const txt=r?r.value:[...s.querySelectorAll('input')].map(f=>f.value).filter(Boolean).join(" · ");
const li=document.createElement("li");
li.innerHTML='<span>'+s.dataset.key+'</span><b>'+(txt||"")+'</b>';
sum.appendChild(li);
});
}
function show(n,dir){
steps.forEach(s=>{s.classList.remove("on","back");});
const s=steps[n];
if(dir<0)s.classList.add("back");
s.classList.add("on");
i=n;
bar.style.transform="scaleX("+(Math.min(i,review)/review).toFixed(3)+")"; // transform ולא width: לא מחולל layout
num.textContent=i<last?("שלב "+(i+1)+" מתוך "+(review+1)):"";
back.style.visibility=(i===0||i===last)?"hidden":"visible";
next.textContent=i===review?"שליחה":"המשך";
form.querySelector(".ms-nav").style.display=i===last?"none":"flex";
if(i===review)paintSummary();
}
next.addEventListener("click",()=>{
if(i<review&&!valid(i)){steps[i].querySelector(".ms-err").classList.add("on");return;}
show(Math.min(i+1,last),1);
});
back.addEventListener("click",()=>show(Math.max(0,i-1),-1));
show(0,1);
})();