Our approach
Today's AI capabilities allow you to quickly develop simple things like forms and integrate them directly into your website as HTML/CSS/JS, which is also beneficial for conversion tracking.
For developers, it also offers the advantage of being able to use the front-end framework of their choice.
To generate such a form, simply copy the attached example into Orbitype Intelligence and request any adjustments. Then maintain it on the website in an “HTML” block.
Example:
Here's a minimal example:
Note: Styles can be overridden by your website's design—you are free to adjust them.
Important: don't forget to fill in your Configuration below the <!-- DEMO --> marker (at the bottom of the code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form Snippet Demo</title>
</head>
<body style="margin:0;padding:0;background:#f8fafc">
<div id="fs-demo" style="height:100vh"></div>
<!-- SNIPPET START -->
<style>
/* === design tokens === */
.fs-root{--fs-primary-50:#f0f4ff;--fs-primary-100:#e1e9ff;--fs-primary-200:#c3d3ff;--fs-primary-400:#87a7ff;--fs-primary-500:#aac1fe;--fs-primary-600:#8ba3e8;--fs-primary-800:#4d67bc;--fs-secondary-50:#f8fafc;--fs-secondary-100:#f1f5f9;--fs-secondary-200:#e2e8f0;--fs-secondary-300:#cbd5e1;--fs-secondary-400:#94a3b8;--fs-secondary-500:#64748b;--fs-secondary-600:#475569;--fs-secondary-700:#334155;--fs-secondary-900:#0f172a;--fs-error-300:#fca5a5;--fs-error-500:#ef4444;--fs-error-600:#dc2626;--fs-success-600:#16a34a;--fs-gray-200:#e5e7eb;--fs-gray-400:#9ca3af;--fs-gray-800:#1f2937;font-family:system-ui,-apple-system,sans-serif;color:var(--fs-secondary-900);box-sizing:border-box}
.fs-root *,.fs-root *::before,.fs-root *::after{box-sizing:inherit;margin:0;padding:0}
/* --- Steps mode: full-height container --- */
.fs-steps{display:flex;flex-direction:column;height:100%;min-height:400px;background:#fff;position:relative;overflow:hidden}
/* Progress bar */
.fs-progress-track{position:absolute;top:0;left:0;right:0;height:3px;background:var(--fs-secondary-100);z-index:10}
.fs-progress-fill{height:100%;background:var(--fs-primary-500);transition:width .3s ease-out}
/* Step content area */
.fs-step-body{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px}
.fs-step-inner{width:100%;max-width:640px}
/* Question label */
.fs-question{font-size:clamp(18px,3vw,28px);font-weight:600;line-height:1.3;color:var(--fs-secondary-900);margin-bottom:32px}
/* --- Choice option cards --- */
.fs-options{display:flex;flex-direction:column;gap:12px}
.fs-option{display:flex;align-items:center;gap:16px;width:100%;padding:16px 20px;border:2px solid var(--fs-secondary-200);border-radius:12px;background:#fff;cursor:pointer;text-align:left;font:inherit;color:var(--fs-secondary-900);transition:all .2s ease;outline:none}
.fs-option:hover{border-color:var(--fs-primary-400);background:var(--fs-primary-50);box-shadow:0 4px 12px rgba(0,0,0,.08)}
.fs-option:focus-visible{border-color:var(--fs-primary-500);box-shadow:0 0 0 3px rgba(170,193,254,.3)}
.fs-option.fs-selected{border-color:var(--fs-primary-500);background:var(--fs-primary-50)}
.fs-option-badge{display:flex;align-items:center;justify-content:center;width:40px;height:40px;flex-shrink:0;border-radius:8px;background:var(--fs-secondary-100);font-size:14px;font-weight:600;color:var(--fs-secondary-700);transition:all .2s}
.fs-option:hover .fs-option-badge,.fs-option.fs-selected .fs-option-badge{background:var(--fs-primary-200);color:var(--fs-primary-800)}
.fs-option-text{flex:1;font-size:clamp(15px,2vw,18px);line-height:1.4}
.fs-option-check{flex-shrink:0;width:24px;height:24px;color:var(--fs-primary-600);display:none}
.fs-option.fs-selected .fs-option-check{display:block}
/* --- Input fields --- */
.fs-input-wrap{display:flex;flex-direction:column;gap:4px}
.fs-label{font-size:14px;font-weight:500;color:var(--fs-secondary-700)}
.fs-label .fs-req{color:var(--fs-error-500);margin-left:4px}
.fs-input,.fs-textarea,.fs-select{display:block;width:100%;padding:12px 16px;font-size:16px;line-height:1.5;font-family:inherit;border:1px solid var(--fs-gray-200);border-radius:8px;background:#fff;color:var(--fs-secondary-900);box-shadow:0 1px 2px rgba(0,0,0,.05);transition:border-color .2s,box-shadow .2s;outline:none}
.fs-input::placeholder,.fs-textarea::placeholder{color:var(--fs-secondary-400)}
.fs-input:focus,.fs-textarea:focus,.fs-select:focus{border-color:var(--fs-primary-500);box-shadow:0 0 0 3px rgba(170,193,254,.35)}
.fs-input.fs-has-error,.fs-textarea.fs-has-error,.fs-select.fs-has-error{border-color:var(--fs-error-300)}
.fs-input.fs-has-error:focus,.fs-textarea.fs-has-error:focus{border-color:var(--fs-error-500);box-shadow:0 0 0 3px rgba(239,68,68,.2)}
.fs-textarea{resize:vertical;min-height:80px}
.fs-select{appearance:none;padding-right:40px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%239ca3af' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center;background-size:20px}
.fs-error-msg{font-size:14px;color:var(--fs-error-600);margin-top:2px}
/* --- Buttons --- */
.fs-btn{display:inline-flex;align-items:center;justify-content:center;height:40px;padding:0 16px;font-size:14px;font-weight:500;font-family:inherit;border-radius:8px;border:none;cursor:pointer;transition:all .2s;outline:none}
.fs-btn:focus-visible{box-shadow:0 0 0 2px #000,0 0 0 4px #fff}
.fs-btn:disabled{opacity:.5;cursor:not-allowed}
.fs-btn-primary{background:#000;color:#fff;box-shadow:0 1px 3px rgba(0,0,0,.12)}
.fs-btn-primary:hover:not(:disabled){background:var(--fs-gray-800);box-shadow:0 4px 8px rgba(0,0,0,.15)}
.fs-btn-lg{height:48px;padding:0 24px;font-size:16px}
/* --- Bottom bar (steps mode) --- */
.fs-bottom{flex-shrink:0;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 24px;border-top:1px solid var(--fs-secondary-100);background:rgba(248,250,252,.5)}
.fs-back-btn{display:inline-flex;align-items:center;gap:8px;font-size:14px;font-family:inherit;color:var(--fs-secondary-600);background:none;border:none;cursor:pointer;padding:4px 0;transition:color .2s}
.fs-back-btn:hover{color:var(--fs-secondary-900)}
.fs-back-btn svg{width:16px;height:16px}
.fs-kbd-hints{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:10px;font-size:12px;color:var(--fs-secondary-400)}
.fs-kbd{display:inline-block;padding:2px 8px;font-family:ui-monospace,monospace;font-size:11px;border:1px solid var(--fs-secondary-300);border-radius:4px;background:#fff}
.fs-kbd-sep{color:var(--fs-secondary-300)}
/* --- Step slide transition --- */
.fs-step-enter{opacity:0;transform:translateX(24px)}
.fs-step-leave{opacity:0;transform:translateX(-24px)}
/* --- Success --- */
.fs-success{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:48px 24px;flex:1;min-height:0;animation:fs-fade-up .5s ease}
.fs-success-icon{width:64px;height:64px;border-radius:50%;background:var(--fs-primary-50);display:flex;align-items:center;justify-content:center;margin-bottom:24px;box-shadow:0 0 0 8px color-mix(in srgb,var(--fs-primary-200) 40%,transparent)}
.fs-success-icon svg{width:32px;height:32px;color:var(--fs-primary-800)}
.fs-success-title{font-size:24px;font-weight:700;color:var(--fs-secondary-900);margin-bottom:8px;line-height:1.3}
.fs-success-sub{font-size:15px;color:var(--fs-secondary-500);max-width:340px;line-height:1.5}
@keyframes fs-fade-up{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
/* --- Classic mode --- */
.fs-classic{display:flex;flex-direction:column;gap:20px;background:#fff;border-radius:12px;padding:32px 28px;box-shadow:0 2px 12px rgba(0,0,0,.06)}
.fs-classic .fs-label{margin-bottom:6px}
.fs-classic .fs-options{gap:8px}
.fs-classic .fs-option{padding:12px 16px}
.fs-classic .fs-option-badge{width:32px;height:32px;font-size:12px;border-radius:6px}
/* --- Mode toggle --- */
.fs-mode-bar{display:flex;justify-content:center;padding:12px 0 0;position:relative;z-index:20}
.fs-steps .fs-mode-bar{position:absolute;top:10px;left:0;right:0;padding:0}
.fs-mode-toggle{display:inline-flex;background:var(--fs-secondary-100);border-radius:8px;padding:3px;gap:2px}
.fs-mode-btn{padding:6px 16px;font-size:13px;font-weight:500;font-family:inherit;border:none;border-radius:6px;cursor:pointer;color:var(--fs-secondary-500);background:transparent;transition:all .2s;outline:none;width:auto}
.fs-mode-btn:hover{color:var(--fs-secondary-700)}
.fs-mode-btn.fs-mode-active{background:#fff;color:var(--fs-secondary-900);box-shadow:0 1px 3px rgba(0,0,0,.1)}
.fs-mode-btn:focus-visible{box-shadow:0 0 0 2px var(--fs-primary-500)}
/* --- Submitting state --- */
.fs-submitting{opacity:.6;pointer-events:none}
/* --- Spinner --- */
@keyframes fs-spin{to{transform:rotate(360deg)}}
.fs-spinner{display:inline-block;width:16px;height:16px;margin-right:8px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:fs-spin .6s linear infinite}
</style>
<script>
/*! FormSnippet v2 — iaha-styled embeddable form → webhook */
;(function(){
"use strict";
var CHECK_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg>';
var BACK_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 19l-7-7 7-7"/></svg>';
class FormSnippet {
constructor(config) {
this.c = Object.assign({
el: '#fs-root',
mode: 'steps',
fields: [],
webhook: '',
submitLabel: 'Submit',
backLabel: 'Back',
nextLabel: 'Next',
successMessage: 'Thank you!',
successSubtitle: '',
onSuccess: null,
onError: null,
}, config);
this.values = {};
this.errors = {};
this.step = 0;
this.submitted = false;
this.submitting = false;
this.focusedOption = -1;
this.modeLocked = false;
this.root = document.querySelector(this.c.el);
if (!this.root) return;
this.root.classList.add('fs-root');
this.c.fields.forEach(function(f) { this.values[f.name] = f.value || ''; }.bind(this));
this._onKey = this._handleKey.bind(this);
this.render();
}
// --- Mode toggle -------------------------------------------------------
_lockMode() {
if (this.modeLocked) return;
this.modeLocked = true;
// Remove toggle from DOM without full re-render
var bar = this.root.querySelector('.fs-mode-bar');
if (bar) bar.remove();
}
_renderModeToggle() {
if (this.modeLocked) return null;
var self = this;
var bar = this._el('div', 'fs-mode-bar');
var toggle = this._el('div', 'fs-mode-toggle');
var btnSteps = this._el('button', 'fs-mode-btn' + (this.c.mode === 'steps' ? ' fs-mode-active' : ''));
btnSteps.textContent = 'Steps';
btnSteps.type = 'button';
btnSteps.onclick = function() { if (self.c.mode !== 'steps') { self.c.mode = 'steps'; self.step = 0; self.render(); } };
var btnClassic = this._el('button', 'fs-mode-btn' + (this.c.mode === 'classic' ? ' fs-mode-active' : ''));
btnClassic.textContent = 'Classic';
btnClassic.type = 'button';
btnClassic.onclick = function() { if (self.c.mode !== 'classic') { self.c.mode = 'classic'; self.step = 0; self.render(); } };
toggle.appendChild(btnSteps);
toggle.appendChild(btnClassic);
bar.appendChild(toggle);
return bar;
}
// --- Render -----------------------------------------------------------
render() {
this.root.innerHTML = '';
if (this.submitted) { this._renderSuccess(); return; }
if (this.c.mode === 'steps') this._renderSteps();
else this._renderClassic();
}
_renderSteps() {
var wrap = this._el('div', 'fs-steps' + (this.submitting ? ' fs-submitting' : ''));
// Progress bar
var track = this._el('div', 'fs-progress-track');
var fill = this._el('div', 'fs-progress-fill');
var pct = this.c.fields.length > 0 ? ((this.step + 1) / this.c.fields.length) * 100 : 0;
fill.style.width = pct + '%';
track.appendChild(fill);
wrap.appendChild(track);
// Mode toggle
var toggleBar = this._renderModeToggle();
if (toggleBar) wrap.appendChild(toggleBar);
// Step body
var body = this._el('div', 'fs-step-body');
var inner = this._el('div', 'fs-step-inner');
var field = this.c.fields[this.step];
var q = this._el('h2', 'fs-question');
q.textContent = field.label;
inner.appendChild(q);
inner.appendChild(this._renderField(field));
// Input step: add next/submit button below (not for card-rendered fields)
var isCardField = field.type === 'choice' || field.type === 'select';
if (!isCardField) {
var btn = this._el('button', 'fs-btn fs-btn-primary fs-btn-lg');
btn.style.marginTop = '16px';
var isLast = this.step === this.c.fields.length - 1;
btn.textContent = isLast ? this.c.submitLabel : this.c.nextLabel;
var val = (this.values[field.name] || '').trim();
if (field.required && !val) btn.disabled = true;
btn.onclick = isLast ? this.submit.bind(this) : this.next.bind(this);
inner.appendChild(btn);
}
body.appendChild(inner);
wrap.appendChild(body);
// Bottom bar
var bottom = this._el('div', 'fs-bottom');
if (this.step > 0) {
var back = this._el('button', 'fs-back-btn');
back.innerHTML = BACK_SVG + ' ' + this.c.backLabel;
back.onclick = this.prev.bind(this);
bottom.appendChild(back);
} else {
bottom.appendChild(this._el('div'));
}
// Keyboard hints
var hints = this._el('div', 'fs-kbd-hints');
if (field.type === 'choice' || field.type === 'select') {
hints.innerHTML =
'<span>' + this._kbd('\u2191\u2193') + ' Navigate</span>' +
'<span class="fs-kbd-sep">\u00b7</span>' +
'<span>' + this._kbd('Enter') + ' Select</span>' +
'<span class="fs-kbd-sep">\u00b7</span>' +
'<span>' + this._kbd('A') + ' \u2013 ' + this._kbd(String.fromCharCode(64 + Math.min(26, (field.options || []).length))) + ' Direct</span>';
} else {
hints.innerHTML = '<span>' + this._kbd('\u2190') + ' Back</span>';
}
bottom.appendChild(hints);
bottom.appendChild(this._el('div'));
wrap.appendChild(bottom);
this.root.appendChild(wrap);
document.removeEventListener('keydown', this._onKey);
document.addEventListener('keydown', this._onKey);
}
_renderClassic() {
var form = this._el('div', 'fs-classic' + (this.submitting ? ' fs-submitting' : ''));
var self = this;
// Mode toggle
var toggleBar = this._renderModeToggle();
if (toggleBar) form.appendChild(toggleBar);
this.c.fields.forEach(function(f) {
var fieldWrap = self._el('div');
var isCards = f.type === 'choice';
var label = self._el('label', 'fs-label');
label.textContent = f.label;
if (f.required) { var req = self._el('span', 'fs-req'); req.textContent = isCards ? ' *' : '*'; label.appendChild(req); }
fieldWrap.appendChild(label);
fieldWrap.appendChild(self._renderField(f));
fieldWrap.style.display = 'flex';
fieldWrap.style.flexDirection = 'column';
fieldWrap.style.gap = '4px';
form.appendChild(fieldWrap);
});
var btn = this._el('button', 'fs-btn fs-btn-primary fs-btn-lg');
btn.style.width = '100%';
btn.style.marginTop = '8px';
if (this.submitting) { btn.innerHTML = '<span class="fs-spinner"></span>Sending...'; btn.disabled = true; }
else btn.textContent = this.c.submitLabel;
btn.onclick = this.submit.bind(this);
form.appendChild(btn);
this.root.appendChild(form);
}
_renderField(field) {
var self = this;
var val = this.values[field.name] || '';
// Render as option cards: choice always, select in steps mode
var renderAsCards = field.type === 'choice' || (field.type === 'select' && this.c.mode === 'steps');
if (renderAsCards) {
var row = this._el('div', 'fs-options');
(field.options || []).forEach(function(opt, i) {
var isSelected = val === opt;
var btn = self._el('button', 'fs-option' + (isSelected ? ' fs-selected' : '') + (self.focusedOption === i && self.c.mode === 'steps' ? ' fs-selected' : ''));
btn.type = 'button';
var badge = self._el('span', 'fs-option-badge');
badge.textContent = String.fromCharCode(65 + i);
btn.appendChild(badge);
var text = self._el('span', 'fs-option-text');
text.textContent = opt;
btn.appendChild(text);
var check = self._el('span', 'fs-option-check');
check.innerHTML = CHECK_SVG;
btn.appendChild(check);
btn.onclick = function() {
self._lockMode();
self.values[field.name] = opt;
self.errors[field.name] = '';
if (self.c.mode === 'steps') {
// Auto-advance on choice selection (typeform behavior)
var isLast = self.step === self.c.fields.length - 1;
if (isLast) self.submit();
else { self.step++; self.focusedOption = -1; self.render(); }
} else {
self.render();
}
};
btn.onmouseenter = function() { if (self.c.mode === 'steps') { self.focusedOption = i; } };
btn.onmouseleave = function() { if (self.c.mode === 'steps') { self.focusedOption = -1; } };
row.appendChild(btn);
});
return row;
}
var wrap = this._el('div', 'fs-input-wrap');
if (field.type === 'textarea') {
var ta = this._el('textarea', 'fs-textarea' + (this.errors[field.name] ? ' fs-has-error' : ''));
ta.placeholder = field.placeholder || '';
ta.value = val;
ta.oninput = function() {
self._lockMode();
self.values[field.name] = ta.value;
self.errors[field.name] = '';
// Update button disabled state in steps mode
if (self.c.mode === 'steps') self._updateStepBtn(field);
};
wrap.appendChild(ta);
} else if (field.type === 'select') {
var sel = this._el('select', 'fs-select' + (this.errors[field.name] ? ' fs-has-error' : ''));
var ph = this._el('option'); ph.value = ''; ph.textContent = field.placeholder || 'Select...'; ph.disabled = true; ph.selected = !val;
sel.appendChild(ph);
(field.options || []).forEach(function(opt) {
var o = self._el('option'); o.value = opt; o.textContent = opt; if (val === opt) o.selected = true;
sel.appendChild(o);
});
sel.onchange = function() {
self._lockMode();
self.values[field.name] = sel.value;
self.errors[field.name] = '';
if (self.c.mode === 'steps') self._updateStepBtn(field);
};
wrap.appendChild(sel);
} else {
var inp = this._el('input', 'fs-input' + (this.errors[field.name] ? ' fs-has-error' : ''));
inp.type = field.type || 'text';
inp.placeholder = field.placeholder || '';
inp.value = val;
if (field.autocomplete) inp.autocomplete = field.autocomplete;
if (field.name) inp.name = field.name;
inp.oninput = function() {
self._lockMode();
self.values[field.name] = inp.value;
self.errors[field.name] = '';
if (self.c.mode === 'steps') self._updateStepBtn(field);
};
inp.onkeydown = function(e) {
if (e.key === 'Enter') { e.preventDefault(); var isLast = self.step === self.c.fields.length - 1; isLast ? self.submit() : self.next(); }
};
wrap.appendChild(inp);
setTimeout(function() { inp.focus(); }, 50);
}
if (this.errors[field.name]) {
var err = this._el('div', 'fs-error-msg');
err.textContent = this.errors[field.name];
wrap.appendChild(err);
}
return wrap;
}
_renderSuccess() {
var cls = this.c.mode === 'steps' ? 'fs-steps' : 'fs-classic';
var box = this._el('div', cls);
if (cls === 'fs-classic') { box.style.minHeight = '360px'; box.style.justifyContent = 'center'; box.style.alignItems = 'center'; }
var msg = this._el('div', 'fs-success');
/* checkmark icon */
var iconWrap = this._el('div', 'fs-success-icon');
iconWrap.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/></svg>';
msg.appendChild(iconWrap);
/* title */
var title = this._el('div', 'fs-success-title');
title.textContent = this.c.successMessage || 'Thank you!';
msg.appendChild(title);
/* subtitle */
if (this.c.successSubtitle) {
var sub = this._el('div', 'fs-success-sub');
sub.textContent = this.c.successSubtitle;
msg.appendChild(sub);
}
box.appendChild(msg);
this.root.appendChild(box);
document.removeEventListener('keydown', this._onKey);
}
_updateStepBtn(field) {
var btn = this.root.querySelector('.fs-btn-primary');
if (!btn || !field.required) return;
var val = (this.values[field.name] || '').trim();
btn.disabled = !val;
}
// --- Validation -------------------------------------------------------
validate(name) {
var self = this;
var fields = name ? this.c.fields.filter(function(f) { return f.name === name; }) : this.c.fields;
var valid = true;
fields.forEach(function(f) {
var v = (self.values[f.name] || '').trim();
if (f.required && !v) { self.errors[f.name] = 'This field is required'; valid = false; return; }
if (f.type === 'email' && v && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) { self.errors[f.name] = 'Please enter a valid email'; valid = false; return; }
if (f.pattern && v && !f.pattern.test(v)) { self.errors[f.name] = 'Invalid format'; valid = false; return; }
self.errors[f.name] = '';
});
return valid;
}
// --- Navigation -------------------------------------------------------
next() {
var field = this.c.fields[this.step];
if (!this.validate(field.name)) { this.render(); return; }
if (this.step < this.c.fields.length - 1) { this.step++; this.focusedOption = -1; this.render(); }
}
prev() {
if (this.step > 0) { this.step--; this.focusedOption = -1; this.render(); }
}
// --- Submit ------------------------------------------------------------
submit() {
var self = this;
if (!this.validate()) { this.render(); return; }
if (!this.c.webhook) { this.submitted = true; this.render(); if (this.c.onSuccess) this.c.onSuccess(this.values); return; }
this.submitting = true;
this.render();
fetch(this.c.webhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.values),
})
.then(function(res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
self.submitting = false;
self.submitted = true;
self.render();
if (self.c.onSuccess) self.c.onSuccess(self.values);
})
.catch(function(err) {
self.submitting = false;
self.render();
if (self.c.onError) self.c.onError(err);
else console.error('FormSnippet submit error:', err);
});
}
// --- Keyboard ----------------------------------------------------------
_handleKey(e) {
if (this.c.mode !== 'steps' || this.submitted || this.submitting) return;
var field = this.c.fields[this.step];
if (!field) return;
// Back: ArrowLeft or Backspace (if not in an input)
if (e.key === 'ArrowLeft' || e.key === 'Backspace') {
if (document.activeElement && document.activeElement.tagName === 'INPUT') return;
e.preventDefault(); this.prev(); return;
}
if (field.type === 'choice' || field.type === 'select') {
var opts = field.options || [];
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
e.preventDefault();
this.focusedOption = this.focusedOption < opts.length - 1 ? this.focusedOption + 1 : 0;
this.render(); return;
}
if (e.key === 'ArrowUp') {
e.preventDefault();
this.focusedOption = this.focusedOption > 0 ? this.focusedOption - 1 : opts.length - 1;
this.render(); return;
}
if (e.key === 'Enter' && this.focusedOption >= 0 && this.focusedOption < opts.length) {
e.preventDefault();
this._lockMode();
this.values[field.name] = opts[this.focusedOption];
this.errors[field.name] = '';
var isLast = this.step === this.c.fields.length - 1;
if (isLast) this.submit(); else { this.step++; this.focusedOption = -1; this.render(); }
return;
}
// Letter keys A-Z
var letter = e.key.toUpperCase();
var idx = letter.charCodeAt(0) - 65;
if (letter.length === 1 && idx >= 0 && idx < opts.length) {
e.preventDefault();
this._lockMode();
this.values[field.name] = opts[idx];
this.errors[field.name] = '';
var isLast = this.step === this.c.fields.length - 1;
if (isLast) this.submit(); else { this.step++; this.focusedOption = -1; this.render(); }
}
}
}
// --- Helpers -----------------------------------------------------------
_el(tag, cls) { var el = document.createElement(tag); if (cls) el.className = cls; return el; }
_kbd(text) { return '<span class="fs-kbd">' + text + '</span>'; }
}
window.FormSnippet = FormSnippet;
})();
</script>
<!-- SNIPPET END -->
<!-- DEMO -->
<script>
new FormSnippet({
el: '#fs-demo',
mode: 'steps',
webhook: 'https://core.orbitype.com/workflows/webhook?webhookSecret={{place your orbitype webhook id here}}',
fields: [
{ name: 'name', label: 'What is your name?', type: 'text', required: true, placeholder: 'Jane Doe', autocomplete: 'name' },
{ name: 'plan', label: 'Which plan interests you?', type: 'choice', options: ['Starter', 'Professional', 'Enterprise'], required: true },
{ name: 'company_size', label: 'How large is your company?', type: 'select', options: ['1-10', '11-50', '51-200', '200+'], required: true, placeholder: 'Select company size...' },
{ name: 'message', label: 'Anything else you want us to know?', type: 'textarea', placeholder: 'Tell us more...' },
{ name: 'email', label: 'What is your email address?', type: 'email', required: true, placeholder: 'jane@company.com', autocomplete: 'email' },
],
submitLabel: 'Send',
nextLabel: 'Next',
backLabel: 'Back',
successMessage: 'Thanks! We will be in touch.',
successSubtitle: 'We have received your information and will get back to you shortly.',
onSuccess: function(values) { console.log('Form submitted:', values); },
onError: function(err) { console.error('Form error:', err); },
});
</script>
</body>
</html>