Solutions

Revenue Operations OS

Run your full revenue engine in one operating system across sales, marketing, and RevOps workflows.

AI Agents for Revenue Teams

Deploy AI agents that execute revenue tasks across prospecting, qualification, and pipeline operations.

Workflow Automation

Automate repetitive GTM workflows end-to-end with orchestration, triggers, and real-time actions.

Lead Generation Automation

Capture and create more qualified pipeline with automated inbound and outbound lead generation flows.

Outreach Automation

Scale personalized multi-step outreach sequences without losing quality or response relevance.

LinkedIn Account Automation

Automate LinkedIn account activity, outreach cadence, and engagement workflows with control.

CRM Automation

Keep CRM data clean, synchronized, and actionable with automated updates and process automation.

Lead Enrichment

Enrich lead records automatically with firmographic and behavioral data to improve conversion rates.

ICP Scoring

Prioritize the highest-fit accounts with dynamic ICP scoring powered by your GTM data.

Agentic Web Crawling & Research

Research the web with autonomous agents that find, structure, and sync intelligence into your systems.

Sales Pipeline Automation

Move deals faster with automated stage progression, reminders, and pipeline orchestration.

Marketing Automation

Automate campaign execution and performance loops across channels to increase marketing output.

Content Management & Publishing

Plan, generate, and publish content workflows from one system with AI-powered execution.

Operations & Workflow Automation

Automate operational handoffs and internal processes across teams with reliability and control.

Customer Success & Support

Support customers proactively with automated follow-up, success playbooks, and retention workflows.

Solutions

With orbitype you get it all

asd

Forms & Interactive calculators

Nowadays, you no longer need to install your own software to integrate forms.

//

TABLE_OF_CONTENTS

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>

Learn more

Cookbook - Sections

Modern webpages are often composed of many different sections.

Things like hero banners, testimonials, FAQs or simple text blocks.

Developers implement these as reusable components with props.

But how do we allow non-technical people to use them in Orbitype?

Cancel Account

To ensure you’re comfortable using Orbitype, we want to make sure everything is clear. If you have any questions or encounter issues understanding how something works, please don’t hesitate to reach out. We’re here to assist you—just send us a message at support@orbitype.com, and we’ll be happy to help you with any questions you have.