Back to Widget Patterns
v2.2Lead Capture

Inline Multi-Field Form

A 3 to 5 field form for sales lead capture. Per-field configuration (text, email, tel, url, select) with per-field validation. Grid, stacked, or horizontal layout.

Live preview

Rendered with real component code. Each example demonstrates a documented variant.

Default grid

Stacked layout

With select field

No spam. We will reach out within one business day.

React props

Configure the component via props in React or via attributes in HTML.

PropTypeDefaultDescription
fieldsFormField[]requiredOrdered list of form fields to render (3 to 5 recommended)
onSubmit(values: Record<string, string>) => void | Promise<void>requiredCalled with all field values on valid submission; may return a Promise
buttonLabelstring"Get in touch"Submit button label
layout"horizontal" | "stacked" | "grid""grid"Controls field arrangement; grid uses two columns on wide viewports
disclaimerTextstringundefinedOptional fine-print shown below the button
successTextstring"Thanks, we will be in touch."Text shown after a successful submission
errorTextstring"Something went wrong. Check your entries and try again."Text shown when the onSubmit handler throws
classNamestringundefinedExtra class applied to the root form element

HTML usage

The HTML variant uses the same shared styles and class names. Drop into any stack.

<link rel="stylesheet" href="path/to/inline-multi-field-form/styles.css">

<form class="imff imff--grid" id="my-lead-form" novalidate>
  <div class="imff__fields">
    <div class="imff__field">
      <label class="imff__label" for="imff-name">Full name</label>
      <input class="imff__input" id="imff-name" type="text" name="name"
             placeholder="Jane Smith" autocomplete="name" required aria-required="true">
      <p class="imff__error" id="imff-name-error" role="alert" hidden></p>
    </div>

    <div class="imff__field">
      <label class="imff__label" for="imff-email">Work email</label>
      <input class="imff__input" id="imff-email" type="email" name="email"
             placeholder="jane@company.com" autocomplete="email" required aria-required="true">
      <p class="imff__error" id="imff-email-error" role="alert" hidden></p>
    </div>

    <div class="imff__field">
      <label class="imff__label" for="imff-company">Company</label>
      <input class="imff__input" id="imff-company" type="text" name="company"
             placeholder="Acme Corp" autocomplete="organization" required aria-required="true">
      <p class="imff__error" id="imff-company-error" role="alert" hidden></p>
    </div>

    <div class="imff__field">
      <label class="imff__label" for="imff-size">Company size</label>
      <select class="imff__select" id="imff-size" name="size" required aria-required="true">
        <option value="">Select company size</option>
        <option value="1-10">1-10 employees</option>
        <option value="11-50">11-50 employees</option>
        <option value="51-200">51-200 employees</option>
        <option value="201-1000">201-1,000 employees</option>
        <option value="1001+">1,001+ employees</option>
      </select>
      <p class="imff__error" id="imff-size-error" role="alert" hidden></p>
    </div>
  </div>

  <div class="imff__actions">
    <button class="imff__button" type="submit">Request a demo</button>
    <p class="imff__disclaimer">No spam. We will reach out within one business day.</p>
  </div>

  <p class="imff__success" id="imff-success" hidden aria-live="polite">
    Thanks, we will be in touch.
  </p>
</form>

<script>
(function () {
  var form = document.getElementById('my-lead-form');
  var inputs = form.querySelectorAll('.imff__input, .imff__select');
  var button = form.querySelector('.imff__button');
  var success = document.getElementById('imff-success');

  form.addEventListener('submit', function (e) {
    e.preventDefault();
    var values = {};
    inputs.forEach(function (el) { values[el.name] = el.value.trim(); });

    button.disabled = true;
    button.textContent = 'Sending...';

    fetch('/api/leads', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(values),
    })
      .then(function (res) {
        if (!res.ok) throw new Error('Request failed');
        success.hidden = false;
        form.reset();
      })
      .catch(function () {
        button.disabled = false;
        button.textContent = 'Request a demo';
      });
  });
}());
</script>

Customization

Override these CSS custom properties to integrate the component into your brand.

.imff {
  --imff-gap: 1rem;
  --imff-label-color: var(--brand-ink, #111827);
  --imff-label-font-size: 0.875rem;
  --imff-label-font-weight: 500;
  --imff-input-border: var(--brand-border, #d1d5db);
  --imff-input-border-radius: 0.375rem;
  --imff-input-padding-y: 0.625rem;
  --imff-input-padding-x: 0.875rem;
  --imff-input-font-size: 1rem;
  --imff-input-focus-ring: var(--brand-accent, #1e5fcf);
  --imff-grid-cols-desktop: 2;
  --imff-button-bg: var(--brand-accent, #1e5fcf);
  --imff-button-bg-hover: color-mix(in srgb, var(--imff-button-bg) 88%, black);
  --imff-button-text: #ffffff;
  --imff-button-font-size: 1rem;
  --imff-button-font-weight: 600;
  --imff-disclaimer-color: var(--brand-muted, #6b7280);
  --imff-disclaimer-font-size: 0.8125rem;
  --imff-success-color: var(--brand-success, #16a34a);
  --imff-error-color: var(--brand-error, #dc2626);
}