Form Partial

The form is built in the CMS; leed/form renders it and wires it to submission handling. Field types, labels, options and validation are chosen in the Forms builder — this page documents what each of those choices emits, so you can style it, place it, and know what will arrive on the other side.

Forms are free on every plan. There is no tier gate on building one, rendering one, or receiving what it collects.

Calling it

{{> leed/form formId="72339fba" }}

:::warning There is no {{> leed-form }} The partial is leed/form. leed-form is a CSS class on the <form> element. An include that names a partial which does not exist renders as an empty string, with no error — so a typo here looks exactly like a form that failed to load. :::

There are three ways a form reaches a page, and which one you use depends on who decides where the form goes.

Omit formId entirely and the partial reads it from the page’s own data:

{{> leed/form }}

This is the landing-page pattern. The layout is fixed, and an editor chooses which form appears on each page from the CMS without touching a template. Use it when the form’s position is a design decision and the form’s identity is an editorial one.

What it needs

The partial reads three things out of the build’s data:

  • leedForms, from src/_data/leedForms.json — CMS-written, one entry per published form, keyed by formId. The whole file is replaced on every form publish, so hand-editing it does not survive.
  • leed-form-data.countries — Leed’s own country list, used by countries fields. It ships with the site builder rather than coming from your repository.
  • @root, for the Turnstile site key.

The container

Top to bottom, this is what wraps every form:

<div class="leed-form-container inline-form-container">
  <div id="72339fba-heading" class="leed-form-heading">Talk to us</div>
  <div id="72339fba-wb" style="display: none;" class="leed-form-welcome-back">Welcome back, Ada.</div>
  <div id="72339fba-details" class="leed-form-details">We reply within one business day.</div>
  <form data-id="72339fba" class="LeedForm leed-form inline-form" id="72339fba">
    …
  </form>
</div>

The heading, welcome-back and details divs each appear only when the form defines the matching value; the welcome-back div is tied to leedAutofill rather than to its own message, and is hidden until the autofill script recognizes a returning visitor.

The <form> element carries three classes, and the template’s own comment explains why they are separate:

ClassWho owns itPurpose
LeedFormLeedThe submit hook. The bundled JavaScript attaches to the submit event of every element with this class. Remove it and the form posts nowhere.
leed-formyouPure styling. This is the class to write CSS against.
<formPrototype>youThe reusable style name set on the form in the CMS, so one visual treatment can be shared across many forms. The container div also gets <formPrototype>-container.

id and data-id both carry the formId, and every id inside the form is prefixed with it — which is what lets two different forms coexist on one page.

Field types

Each field is wrapped in <div class="leed-form-block <type> known-valid-hideable">. The known-valid-hideable marker is added to every field except one named terms, which is how a consent checkbox stays visible when other already-known fields are hidden from a returning visitor.

typeEmitsOptions honoredNotes
text<input type="text">—placeHolder, pattern, value, oninput, onchange, required
email<input type="email">—Same attribute set as text
tel<input type="tel">—Same attribute set as text
textarea<textarea>—rows, cols, multiple; value becomes the element’s initial content
select<select> with one <option> per entryoptions[] (value, label)A placeHolder becomes a leading <option value="">
countries<select> populated from Leed’s country listignores the field’s own options[]A matching field value preselects that country. The list is shipped by the site builder, not by your repository.
checkbox<input type="checkbox">—selected renders checked="checked"; the id includes the value
radio<input type="radio">—Same as checkbox; several fields sharing a name form a group
label<label> containing value, then <br>—Renders text, not an input. Template-only.
hidden<input type="hidden">—Emitted in a second pass after every other field, so hidden inputs always sit at the end of the form. Template-only.
radiogroupone <div class="leed-form-group-option"> per option, each holding an <input>options[] (value, labelBefore, labelAfter, selected)Template-only, and see the warning below
checkboxgroupthe same structure as radiogroupas radiogroupTemplate-only, and see the warning below

Form definition fields

These are the keys on a form that the partial reads. They are exactly the fields the CMS writes into leedForms.json; a form’s internal name and description stay in the CMS and never reach the site.

FieldEffect
headingRenders #<formId>-heading. Escaped.
detailsRenders #<formId>-details below the heading. Escaped.
formPrototypeAdds <value> to the <form> class list and <value>-container to the wrapper
leedAutofillEmits the hidden #<formId>-wb div and a leedAutofill('<formId>') call after the form
welcomeBackMessageThe text inside that welcome-back div
successCallbackWritten as data-successcallback on the <form>
failureCallbackWritten as data-failurecallback on the <form>
autofill"linkedin" loads LinkedIn’s autofill script and maps the recognized fields to it
buttonThe submit button’s default label. Emitted raw, so an icon in the label works.
fields[]The fields, in order

Field object fields

FieldApplies toEffect
typeallSelects the branch above
nameallThe submitted key, and half of the element’s id
valueallInitial value; textarea uses it as content; radio/checkbox use it as the submitted value and in the id; countries uses it to preselect a country
requiredall inputsEmits required="true"
multipleselect, countries, textareaEmits multiple="true"
sizeselect, countriesEmits size="…". Read by the template; the Forms builder does not set it.
rowstextareaEmits rows="…"
colstextareaEmits cols="…". Read by the template; the Forms builder does not set it.
patterntext, tel, email, radio, checkboxEmits a pattern attribute for native validation
placeHoldertext, tel, email, radio, checkboxA placeholder attribute. On select and countries it instead becomes a leading <option value="">.
oninputtext, tel, email, textarea, radio, checkboxInline handler
onchangethe same set, plus the two group typesInline handler; a group passes its own down to every option
selectedradio, checkboxEmits checked="checked"
labelBeforeallA <label for="…"> before the control. Emitted raw.
labelAfterallA <label for="…"> after the control. Emitted raw.
options[]select, and the two group types{ value, label } per entry
A rendered Leed form showing a text field, an email field, a country select and a consent checkbox above the submit button
The complete emitted markup of a four-field form

A form with a heading, details, a formPrototype of inline-form, and four fields — text, email, countries, checkbox. Whitespace normalized; the country list truncated to two entries.

<div class="leed-form-container inline-form-container">
  <div id="72339fba-heading" class="leed-form-heading">Talk to us</div>
  <div id="72339fba-details" class="leed-form-details">We reply within one business day.</div>

  <form data-id="72339fba" class="LeedForm leed-form inline-form" id="72339fba">

    <div class="leed-form-block text known-valid-hideable">
      <label for="72339fba-firstname">First name</label>
      <input id="72339fba-firstname" name="firstname" type="text" placeholder="Ada" required="true">
    </div>

    <div class="leed-form-block email known-valid-hideable">
      <label for="72339fba-email">Work email</label>
      <input id="72339fba-email" name="email" type="email" required="true">
    </div>

    <div class="leed-form-block countries known-valid-hideable">
      <label for="72339fba-country">Country</label>
      <select id="72339fba-country" name="country">
        <option value="">Choose one</option>
        <option value="us">United States</option>
        <option value="ca">Canada</option>
      </select>
    </div>

    <div class="leed-form-block checkbox">
      <input id="72339fba-terms-yes" name="terms" type="checkbox" value="yes">
      <label for="72339fba-terms-yes">I agree to the terms</label>
    </div>

    <button id="72339fba-button" data-default="Send" type="submit" class="flex items-center">
      <div id="72339fba-button-submit" class="leed-form-button-label" style="display: block;">Send</div>
      <div id="72339fba-button-sending" class="leed-form-button-label" style="display: none;">Sending ...</div>
      <div id="72339fba-button-email-sent" class="leed-form-button-label" style="display: none;">Email Sent!</div>
      <div id="72339fba-button-error" class="leed-form-button-label" style="display: none;">Try again!</div>
      <div id="72339fba-button-thanks" class="leed-form-button-label" style="display: none;">Thank you!</div>
      <div id="72339fba-button-downloading" class="leed-form-button-label leed-form-button-spinner !flex items-center" style="display: none !important;">
        <div><i class="leed-form-spinner"></i></div>
        <div>Downloading ...</div>
      </div>
    </button>
    <div class="cf-turnstile" data-sitekey="0x4AAA…" style="display: none;"></div>
  </form>
</div>

<!-- Leed Form generated by https://leed.ai -->

The terms block is the one without known-valid-hideable. The <label> sits before the input for the three data fields because they set labelBefore, and after it for the consent checkbox because it sets labelAfter.

Ids and names

Two attributes do two different jobs, and mixing them up is what breaks label clicking:

  • The id connects a <label for="…"> to its control. It is generated, never authored.
  • The name is what gets submitted. It is yours to choose in the Forms builder, and it is what decides whether the value lands in a contact record or not.

The id comes from the fieldId helper: <formId>-<name> for most fields, and <formId>-<name>-<value> for radio and checkbox, so several members of one group stay unique while sharing a name. fieldId is an ordinary helper you can call yourself — see Form and CTA Helpers.

The names that become a contact

This is the most useful table on the page if you are hand-building a form or renaming fields. Four names are supplied by Leed at submit time; thirteen more map straight into the contact record if you use them; anything else is captured but unmapped.

NameSourceMaps to
formIdadded at submitthe form the submission came from
pidadded at submitthe page it was submitted from
ptadded at submitthat page’s page type
uadded at submitthe visitor identifier
emailyour fieldcontact email
phoneyour fieldcontact phone
companyyour fieldcompany
firstnameyour fieldfirst name
lastnameyour fieldlast name
titleyour fieldjob title
addressyour fieldstreet address
cityyour fieldcity
stateyour fieldstate or region
postalcodeyour fieldpostal code
countryyour fieldcountry
termsyour fieldconsent
productnameyour fieldproduct interest
anything elseyour fieldcaptured with the submission, not mapped to a contact field

Spelling matters exactly as written — all lower case, no separators. firstName and first_name are both “anything else”. What happens to a submission after it arrives, and why a preview site never records one, is at Form Submissions; the contact record itself is at Contacts.

The submit button

There is one <button>, holding six labeled <div>s. The bundled JavaScript shows one at a time by toggling inline display; you style them.

Id suffixShown when
-button-submitat rest — carries the form’s own button text
-button-sendingthe submission is in flight
-button-email-senta response email has been sent
-button-errorthe submission failed
-button-thanksthe submission succeeded
-button-downloadinga gated download is being prepared — this one also carries leed-form-button-spinner and an <i class="leed-form-spinner">

Each is <formId><suffix>, and each carries the class leed-form-button-label. The button’s own data-default attribute holds the resting label so the script can restore it.

Because the script writes inline display, a CSS rule that sets display on .leed-form-button-label will not win. Style color, spacing and typography there; leave visibility to the script.

Turnstile

Every form ends with an invisible Cloudflare Turnstile widget:

<div class="cf-turnstile" data-sitekey="…" style="display: none;"></div>

The site key comes from the turnstileKey helper, which reads deployment.preview.turnstilePublicKey on a preview build and deployment.public.turnstilePublicKey otherwise. If the relevant key is not configured, it falls back to Cloudflare’s always-pass test key 1x00000000000000000000BB — so a form still renders and still submits on a site that has never configured Turnstile. Which build is which is covered at Preview Site vs Live Site.

Autofill and callbacks

Leed autofill. With leedAutofill set, the partial emits the hidden #<formId>-wb welcome-back div and a leedAutofill('<formId>') call after the form. On a return visit the script fills the fields it already knows, reveals the welcome-back message, and — with the known-valid-hideable markers described above — hides the fields it has already collected, leaving the consent checkbox visible.

LinkedIn autofill. With autofill: "linkedin", the partial loads LinkedIn’s autofill script and emits an IN/Form2 script tag mapping your fields to LinkedIn’s. Only the names it recognizes are mapped: firstname, lastname, phone, email, company, title, city, state, country, plus postalcode which maps to LinkedIn’s zip. Another reason to use the mapped names from the table above.

Callbacks. successCallback and failureCallback become data-successcallback and data-failurecallback on the <form>. They name functions the submission script calls; they are set on the form in the CMS, described at Building a Form.

leed/unsubscribe-form

The second form partial is much simpler and takes no parameters:

{{> leed/unsubscribe-form }}

It emits a static <form id="unsubscribeForm" class="leed-form" onsubmit="return submitLeadUnsubscribe();"> with five radio reasons — Too many emails, Content does not meet my needs or interests, Content was not what I expected, I never signed up for these emails, Other reason — all named reason, plus a donotcontact checkbox and an #unsubscribe-button submit. There is nothing to configure: no CMS form backs it, and the field names are fixed because the unsubscribe endpoint expects them.

It is rendered by Leed’s own unsubscribe page. If you want a different unsubscribe experience, you do not parameterize this partial — you replace the whole page, which is one of the six override slots described at Overriding Leed Templates.

Where leed/form sits among the other fifty-three partials Leed injects, and which of them you may call, is at the Leed Partial Index.

ESC