UNPKG

multi-step-form-vanilla

Version:

A modern, customizable multi-step form system with reusable Web Components.

540 lines (480 loc) 18 kB
/* =========================== 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 !important; 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; }