multi-step-form-vanilla
Version:
A modern, customizable multi-step form system with reusable Web Components.
540 lines (480 loc) • 18 kB
CSS
/* ===========================
0) BASE TOKENS & RESETS
=========================== */
:root{
--font: "Mulish", system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
--text: #1f1a24;
--muted: #6b6b75;
--bg: #fafbfc;
--card: #fff;
--primary: #6750A4;
--primary-contrast: #fff;
--stroke: #e6e7ea;
--progress-bg: #ece6f0;
--error: #b3261e;
--warning: #b45309;
--info: #1d4ed8;
--success: #047857;
--radius: 12px;
--shadow: 0 10px 24px rgba(0,0,0,.06);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body{
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ===========================
1) LAYOUT: container & grid
=========================== */
.container{
max-width: 960px;
padding: 0 16px;
margin: 32px auto 48px;
}
.app-title{ margin: 0 0 16px; font-weight: 700; letter-spacing:.2px; }
.row{
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
@media (min-width:768px){
.row{ grid-template-columns: 1fr 1fr; }
}
.col-12{ grid-column: 1 / -1; }
/* ===========================
2) CARD (multi-step container)
=========================== */
multi-step-form{
display:block;
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
/* overflow: hidden; */
}
/* Progress bar */
#progressbar{
--msf-steps: 4;
display: flex;
padding: 20px 10px 12px;
margin: 0;
counter-reset: step;
border-bottom: 1px solid var(--stroke);
}
#progressbar li{
list-style: none;
width: calc(100% / var(--msf-steps));
text-align: center;
color: var(--muted);
font: 600 13px/1 var(--font);
position: relative;
}
#progressbar li:before{
content: counter(step);
counter-increment: step;
width: 38px; height: 38px; line-height: 38px;
display: block;
background: var(--progress-bg);
color: var(--muted);
border-radius: 50%;
margin: 0 auto 8px;
font-weight: 700;
position: relative; /* sayı hattın üzerinde */
z-index: 4;
transition: transform .25s ease, background .25s ease, color .25s ease;
}
#progressbar li:after{
content:"";
position:absolute; top: 17px; left:-50%; width:100%; height:4px;
background: var(--progress-bg);
}
#progressbar li:first-child:after{ content: none; }
#progressbar li .title{ margin-top:6px; font-size:.92rem; color: var(--muted); transition: color .25s; }
#progressbar li.active:before, #progressbar li.active:after{ background: var(--primary); color: var(--primary-contrast); }
#progressbar li.active .title{ color: var(--primary); }
#progressbar li.active:before{ transform: scale(1.06); }
/* Steps */
multi-step-form step{
display:none;
padding: 24px 24px 18px;
animation: stepIn .28s ease;
}
multi-step-form step.active{ display:block; }
@keyframes stepIn{ from{opacity:0; transform: translateY(6px);} to{opacity:1; transform: translateY(0);} }
/* ===========================
3) BASE FORM STYLES
=========================== */
.field{ margin: 12px 0 16px; }
.field label{ display:block; font-weight: 600; margin-bottom: 8px; color: var(--text); }
.field input,
.field select,
.field textarea{
width: 100%;
padding: 12px 14px;
border: 1px solid var(--stroke);
border-radius: var(--radius);
background: #fff;
color: var(--text);
font-size: 15px;
outline: none;
transition: border-color .15s, box-shadow .2s, background .25s;
}
.field input:focus,
.field select:focus,
.field textarea:focus{
border-color: var(--primary);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 22%, transparent);
}
/* select arrow (native fallback) */
.field.select-wrapper{ position: relative; }
.field.select-wrapper::after{
content:"▾";
position:absolute; right:12px; top:50%;
transform: translateY(-50%);
pointer-events:none; color: var(--muted);
}
/* inline error */
.error-text{ margin-top: 6px; font-size: .9rem; color: var(--error); }
/* Actions (buttons) */
.actions{
display:flex; justify-content:center; gap:10px; padding: 4px 0 20px;
}
.actions button{
background: var(--primary);
color: var(--primary-contrast);
border:1px solid var(--primary);
border-radius: 999px;
min-width: 140px;
padding: 12px 18px;
font: 700 13px/1 var(--font);
letter-spacing:.25px;
text-transform: uppercase;
cursor:pointer;
transition: transform .06s, filter .2s;
}
.actions button:hover{ transform: translateY(-1px); }
.actions .prev{
background:#fff; color: var(--muted); border-color: var(--stroke);
}
.actions .prev:hover{ filter: brightness(0.98); }
/* Code boxes */
.code-group .code-box{
width: 48px; text-align:center; font-size: 22px; padding: 11px 0;
border:1px solid var(--stroke); border-radius: var(--radius);
background: #fff; color: var(--text);
}
/* ===========================
4) ALERTS (component + manual)
=========================== */
.alert{
display:flex; gap:10px; align-items: flex-start;
border-radius: var(--radius);
padding: 12px 14px;
border: 1px solid;
background: #fff;
margin: 10px 0 0;
}
.alert .icon-box{
width: 20px; height: 20px; display: inline-block; flex: 0 0 20px;
/* içine kendi SVG’ni koyacaksın */
}
.alert .alert-content{ line-height: 1.45; }
.alert-info{ border-color:#93c5fd; background: #eef5ff; color:#1d4ed8; }
.alert-success{ border-color:#86efac; background: #ecfdf5; color:#047857; }
.alert-warning{ border-color:#fdba74; background: #fff7ed; color:#b45309; }
.alert-error{ border-color:#fecaca; background: #fef2f2; color:#b3261e; }
/* ===========================
5) COMPONENT-SPECIFIC STYLES
=========================== */
/* form-title */
.form-title h3{ text-align:center; margin: 0 0 6px; font-size: 1.25rem; }
.form-title .desc{ text-align:center; margin: 0 0 16px; color: var(--muted); }
/* form-file (full width dashed) */
.file-drop{
width: 100%;
border: 2px dashed var(--stroke);
border-radius: var(--radius);
padding: 24px;
background: #fff;
display:grid; gap:8px;
cursor: pointer;
}
.file-drop:hover{ background: #fcfcfe; }
.file-drop .file-placeholder{ color: var(--muted); font-size: .95rem; }
.file-list{ color: var(--muted); font-size: .92rem; }
/* form-checkbox */
.checkbox-wrap{
display:flex; gap:12px; align-items:flex-start;
padding: 8px 2px;
}
.checkbox-wrap input[type="checkbox"]{ margin-top: 3px; width: auto; }
.checkbox-wrap .desc{ color: var(--muted); font-size: .95rem; }
/* form-quest (option cards grid) */
.quest{
display:grid; gap: 12px;
grid-template-columns: 1fr;
}
@media (min-width:768px){
.quest{ grid-template-columns: 1fr 1fr; }
}
.quest-card{
border: 1px solid var(--stroke);
border-radius: var(--radius);
padding: 14px;
display:grid; grid-template-columns: 32px 1fr; gap:12px;
background:#fff; cursor: pointer;
transition: border-color .15s, box-shadow .2s, transform .06s;
}
.quest-card:hover{ transform: translateY(-1px); }
.quest-card .icon-box{
width: 24px; height: 24px; border-radius: 6px; display:inline-block; background: #f2f1f7;
}
.quest-card .title{ font-weight: 700; margin-bottom: 4px; }
.quest-card .desc{ color: var(--muted); font-size:.94rem; }
.quest-card.selected{
border-color: var(--primary);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 18%, transparent);
}
/* tek seçenek durumunda full width kalır; grid zaten 1 sütun */
.quest.single{ grid-template-columns: 1fr; }
/* form-select (hierarchical searchable) */
/* form-select (dropdown) */
.fs{
position: relative;
}
.fs .fs-control{
display:flex; align-items:center; justify-content:space-between; gap:8px;
border:1px solid var(--stroke); border-radius: var(--radius);
background:#fff; padding: 10px 12px; cursor: pointer;
}
.fs .fs-control .fs-value{
color: var(--muted);
}
.fs .fs-control.active{
border-color: var(--primary);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 18%, transparent);
}
.fs .fs-caret{ opacity:.6; }
.fs .fs-panel{
position: absolute; left:0; right:0; top: calc(100% + 6px);
background:#fff; border:1px solid var(--stroke); border-radius: 10px;
box-shadow: var(--shadow); z-index: 20; display: none;
dipslay: none; overflow: hidden;
}
.fs.is-open .fs-panel{ display:block; }
.fs .fs-head{
display:flex; flex-wrap:wrap; gap:8px; padding: 10px; border-bottom:1px solid var(--stroke);
background:#fff;
}
.fs .fs-search{ flex: 1 1 220px; }
.fs .fs-search input{
width: 100%; padding: 10px 12px; border:1px solid var(--stroke); border-radius: 8px; outline:none;
}
.fs .fs-breadcrumb{ display:flex; gap:6px; align-items:center; font-size:.92rem; color: var(--muted); }
.fs .crumb{ cursor:pointer; color: var(--primary); }
.fs .sep{ color: var(--muted); }
.fs .fs-body{ max-height: 300px; overflow:auto; padding: 6px; background:#fff; }
.fs .fs-item{
padding: 10px 12px; border-radius: 8px;
display:flex; align-items:center; justify-content:space-between; gap:10px;
cursor:pointer;
}
.fs .fs-item:hover{ background: #faf9ff; }
.fs .fs-item .left{ display:flex; align-items:center; gap:8px; }
.fs .fs-item .icon-box{ width: 18px; height: 18px; display:inline-block; background:#f2f1f7; }
.fs .fs-item .meta{ font-size:.92rem; color: var(--muted); }
.fs .fs-empty{ color: var(--muted); padding: 12px; }
/* hide native controls in our custom blocks */
.quest-card input[type="radio"],
.quest-card input[type="checkbox"]{ display:none; }
/* ===========================
6) BUTTONS
=========================== */
.btn{
display:inline-flex; align-items:center; justify-content:center;
gap:8px; padding: 12px 16px; border-radius: 999px; cursor:pointer;
border:1px solid transparent; font: 700 13px/1 var(--font);
letter-spacing:.25px; text-transform: uppercase; user-select: none;
transition: transform .06s, filter .2s, box-shadow .2s, background .2s, color .2s, border-color .2s;
}
.btn:hover{ transform: translateY(-1px); }
.btn:active{ transform: translateY(0); }
.btn-primary{ background: var(--primary); color: var(--primary-contrast); border-color: var(--primary); }
.btn-success{ background: #16a34a; color: #fff; border-color:#16a34a; }
.btn-warning{ background: #f59e0b; color: #1f2937; border-color:#f59e0b; }
.btn-error{ background: #b3261e; color: #fff; border-color:#b3261e; }
.btn-outline{ background: #fff; color: var(--muted); border-color: var(--stroke); }
.btn-row{ display:flex; flex-wrap: wrap; gap:10px; }
/* ===========================
7) COUNTDOWN (circular)
=========================== */
.countdown{
display:flex; align-items:center; gap:16px; padding: 10px 0;
}
.countdown .ring{
position: relative; width: 72px; height: 72px; flex: 0 0 72px;
}
.countdown svg{
width: 72px; height: 72px; display:block;
transform: rotate(-90deg); /* start at 12 o'clock */
}
.countdown .bg-circle{
stroke: var(--progress-bg); stroke-width: 8; fill: none;
}
.countdown .fg-circle{
stroke: var(--primary); stroke-width: 8; fill: none; stroke-linecap: round;
transition: stroke-dashoffset .4s ease; /* smooth tick */
}
.countdown .time{
position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
font-weight: 700;
}
.countdown .label{
color: var(--muted); font-size: .95rem;
}
.expire-area{ margin-top: 10px; display:none; }
.expire-area.active{ display:block; }
.expire-question{ margin: 8px 0 12px; font-weight: 700; }
/* optional: minimize selection blue flash on svg */
svg, .btn { -webkit-tap-highlight-color: transparent; }
/* ===========================
8) INFINITE FORM
=========================== */
form-infinite {
display:block;
margin:20px 0;
font-family: 'Mulish', sans-serif;
}
/* breadcrumb */
.form-infinite-nav {
margin-bottom:16px; font-size:.9rem;
display:flex; flex-wrap:wrap; align-items:center; gap:6px;
}
.form-infinite-nav form-button { margin-right:6px; }
.form-infinite-nav .crumb {
cursor:pointer; color: var(--primary, #6750a4);
opacity:0; transform:translateX(20px);
animation: slideIn .3s ease forwards;
}
.form-infinite-nav .crumb.active { cursor:default; font-weight:600; color:#222; }
.form-infinite-nav .sep { color:#aaa; }
.form-infinite-nav .back-btn {
font-weight:700; cursor:pointer; color: var(--primary, #6750a4);
margin-right:6px; font-size:1.2rem;
}
.form-infinite-nav .back-btn:hover{ color:#333; }
@keyframes slideIn { to{ opacity:1; transform:translateX(0);} }
/* question */
.inf-question { display:block; }
.inf-question .question-title { font-weight:600; margin-bottom:14px; }
/* option */
.inf-option {
display:flex; align-items:flex-start; gap:12px;
padding:14px; border:1px solid #ddd; border-radius:8px;
margin-bottom:12px; cursor:pointer; transition:all .2s;
}
.inf-option:hover { border-color: var(--primary, #6750a4); }
.inf-option.active { background: var(--primary, #6750a4); color:#fff; border-color: var(--primary, #6750a4); }
.inf-option .icon-box { width:24px; height:24px; background:#eee; flex:0 0 24px; border-radius:4px; }
.inf-option .texts { flex:1; }
.inf-option .title { font-weight:600; }
.inf-option .desc { font-size:.85rem; color:#666; }
/* final */
.final-answer {
padding:16px; border:1px dashed var(--primary, #6750a4);
border-radius:8px; text-align:center;
}
.final-answer .btn-row{ margin-top:12px; display:flex; gap:8px; justify-content:center; }
.fade-in { animation: fadeIn .3s ease; }
@keyframes fadeIn { from{opacity:0; transform:translateY(8px);} to{opacity:1; transform:translateY(0);} }
/* ===========================
9) DATE & TIME PICKER
=========================== */
/* ====== FORM-DATE ====== */
form-date{ display:block; width:100%; }
.form-date{ border:1px solid var(--stroke); border-radius:var(--radius); background:var(--bg); box-shadow:var(--shadow); }
.form-date .fd-head{ display:flex; align-items:center; justify-content:space-between; padding:10px 12px; border-bottom:1px solid var(--stroke); }
.form-date .fd-nav{ display:flex; gap:6px; }
.form-date .fd-btn{
width:34px; height:34px; border:1px solid var(--stroke); border-radius:8px; background:#fff; cursor:pointer;
}
.form-date .fd-btn:disabled{ opacity:.5; cursor:not-allowed; }
.form-date .fd-title{ font-weight:700; color:var(--text); }
.form-date .fd-week{ display:grid; grid-template-columns: repeat(7,1fr); padding:6px 8px; font-size:.85rem; color:var(--muted); text-align:center; }
.form-date .fd-grid{ display:grid; grid-template-columns: repeat(7,1fr); gap:6px; padding: 8px; }
.form-date .fd-day{
padding:10px 0; border:1px solid transparent; border-radius:8px; text-align:center; cursor:pointer; background:#fff;
transition: background .2s, border-color .2s, color .2s, transform .06s;
}
.form-date .fd-day:hover{ background:#f3faf6; }
.form-date .fd-day.fd-disabled{ color:#b9bec5; background:#fafafa; cursor:not-allowed; }
.form-date .fd-day.fd-selected{ background:var(--primary); color:#fff; border-color:var(--primary); font-weight:700; }
.form-date .fd-foot{ padding:8px 12px; border-top:1px solid var(--stroke); font-size:.9rem; color:var(--muted); }
.form-date .fd-error{ padding:8px 12px; color:#b3261e; font-size:.9rem; display:none; }
form-date[data-invalid="true"] .fd-error{ display:block; }
/* ====== FORM-TIME ====== */
form-time{ display:block; width:100%; }
.form-time{ border:1px solid var(--stroke); border-radius:var(--radius); background:var(--bg); box-shadow:var(--shadow); }
.form-time .ft-head{ display:flex; align-items:center; justify-content:space-between; padding:10px 12px; border-bottom:1px solid var(--stroke); }
.form-time .ft-title{ font-weight:700; color:var(--text); }
.form-time .ft-meta{ color:var(--muted); font-size:.9rem; }
.form-time .ft-list{ display:flex; flex-wrap:wrap; gap:8px; padding:10px; max-height:240px; overflow:auto; }
.form-time .ft-slot{
flex:1 1 calc(25% - 8px); min-width:110px; text-align:center;
border:1px solid var(--stroke); border-radius:8px; padding:10px; background:#fff; cursor:pointer;
transition: background .2s, border-color .2s, color .2s, transform .06s;
}
.form-time .ft-slot:hover{ background:#f3faf6; }
.form-time .ft-slot.ft-selected{ background:var(--primary); color:#fff; border-color:var(--primary); font-weight:700; }
.form-time .ft-error{ padding:8px 12px; color:#b3261e; font-size:.9rem; display:none; border-top:1px solid var(--stroke); }
form-time[data-invalid="true"] .ft-error{ display:block; }
/* küçük yardımcı */
.visually-hidden{
position:absolute ; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
/* ortak dropdown yapı */
.dropdown {
position: relative;
}
.dropdown-btn {
width: 100%;
padding: .7rem 1rem;
border:1px solid var(--stroke);
border-radius: var(--radius);
background:#fff;
text-align: left;
cursor:pointer;
font-weight:600;
color: var(--muted);
}
.dropdown-btn.selected { color: var(--text); }
.dropdown-panel {
position:absolute;
top: calc(100% + 6px);
left:0; right:0;
background:#fff;
border:1px solid var(--stroke);
border-radius: var(--radius);
box-shadow: var(--shadow);
opacity:0;
transform: translateY(-5px);
pointer-events:none;
transition: all .25s ease;
z-index:100;
}
.dropdown.open .dropdown-panel {
opacity:1;
transform: translateY(0);
pointer-events:auto;
}
/* takvim ve saat listeleri panel içinde aynı kalabilir */
.form-date, .form-time {
box-shadow:none;
border:none;
border-radius:0;
}