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
React props
Configure the component via props in React or via attributes in HTML.
| Prop | Type | Default | Description |
|---|---|---|---|
| fields | FormField[] | required | Ordered list of form fields to render (3 to 5 recommended) |
| onSubmit | (values: Record<string, string>) => void | Promise<void> | required | Called with all field values on valid submission; may return a Promise |
| buttonLabel | string | "Get in touch" | Submit button label |
| layout | "horizontal" | "stacked" | "grid" | "grid" | Controls field arrangement; grid uses two columns on wide viewports |
| disclaimerText | string | undefined | Optional fine-print shown below the button |
| successText | string | "Thanks, we will be in touch." | Text shown after a successful submission |
| errorText | string | "Something went wrong. Check your entries and try again." | Text shown when the onSubmit handler throws |
| className | string | undefined | Extra 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);
}