settings.fields[] and submits the collected values through the theme services layer.emailProfileId){
"name": "Form",
"view": "Default",
"section": "SectionA",
"settings": {
"showReCaptcha": true,
"emailProfileId": "Email Profile Id",
"description": "Sample text",
"buttonText": "Sample text",
"fields": [
{
"type": "Text",
"name": "sampleField",
"label": "Sample text",
"required": false,
"isHidden": false,
"validationMessage": "",
"placeholder": "Sample text",
"options": [],
"multipleFiles": false
},
"..."
],
"id": "Component Id",
"section": "SectionA",
"type": "NoirForm",
"name": "Form",
"configuredInContentApi": true,
"view": "Default",
"displayName": "",
"cssClass": "",
"header": "Sample text",
"alignment": "Left"
},
"translations": {
"submit": "Sample text",
"choose": "Sample text",
"submitForm": "Sample text",
"...": "..."
}
}settings.idcomp-{{ id }}.settings.fields[]settings.emailProfileIdsettings.cssClass(UNDEFINED).settings.header / settings.descriptionsettings.buttonTexttranslations.submit.settings.showReCaptcha (boolean)true, renders the reCAPTCHA widget and requires a valid response.translations.*isFormValid:disabled="!isFormValid || isSending").fields.skipped inputs).errorsrequired, invalidEmail, fileTooLarge, invalidFileType, recaptcha).isSendinginit()[name]:not(.skipped)blur for most fields → validates on leaving the fieldchange for:select (also toggles placeholder styling)checkbox, radio, file.radio-wrap).checkbox-wrap).skipped (excluded from main fields list)name="<groupName>-required" that is marked requiredfocusout + setTimeout(10) logic ensures validation fires when the user tabs out of the whole group.toggleIsEmptyClass(field)isEmpty class on the nearest .select-wrap depending on whether the select is still on the placeholder option.validateField(field)this.errors[fieldName].valid / .invalid classes on the input or its wrapper.icon-state icon classes when present.checkbox-wrap-required)..radio-wrapinvalidEmail)invalidFileTypefileTooLargeupdateFormValidity().icon-state exists for Text/TextArea/Email fields (label contains the span). It won’t exist for all field types, so the code checks safely.multipleFiles: true), validation currently only checks field.files[0] (first file). You may want to validate all files.updateFormValidity()isFormValid by ensuring every tracked field:errorsclearForm().select-wrap.isEmpty.icon-state).radio-wrap, .checkbox-wrap, .select-wrap)*-required) valueserrors and recomputes isFormValidgetMappedFields(fields)sendEmail endpoint.{ type, name, label, required, value, ... }getLabelText(field) to extract the human label..skipped are included.getLabelText(field)<label for="<field.id>"><span> text content inside the label (because the label includes HTML)for/id pairs correct for every field, otherwise labels become empty.checkForm(showReCaptcha, emailProfileId, successMessage, errorMessage) (async)$store.toast.removeAll())!isFormValidgrecaptcha exists:grecaptcha.getResponse() !== "".g-recaptcha as invalid and sets errors["recaptcha"] = "required"isSending = true{ emailProfileId, fields: getMappedFields(this.fields) }servicesreusabledefault.sendEmail(info)toast.add(..., true))isSendingisSendingshowReCaptcha is true but grecaptcha is missing (script not loaded), the code will skip the check and still submit. If you want strict enforcement, add an else-case that blocks submission when grecaptcha is unavailable.isSending (already implemented via :disabled).$store.toast (user feedback)Alpine.store("toast").removeAll()Alpine.store("toast").add(errorMessage, "ic-warning", "error", true)Alpine.store("toast").add(successMessage, "ic-check", "success")errors["recaptcha"] for inline reCAPTCHA required messaging, but that’s local state (not a global store).grecaptcha. Still, it’s an important cross-cutting dependency when enabled.servicesreusabledefault.sendEmail({ emailProfileId, fields })$store.toastservicesreusabledefault.sendEmail(...)grecaptcha globalGlobalData.Settings.reCaptchaApiKey{field.name}-{index}-{componentId} to keep them unique across the page.x-data="formdefault"checkForm(showReCaptcha, emailProfileId, successMessage, errorMessage)settings.fields[] and renders different markup per field.type, including:.select-wrap).consent wrapper)hasRequiredField)translations.requiredFieldsNoticesettings.showReCaptcha is true:<div class="g-recaptcha" data-sitekey="{{ GlobalData.Settings.reCaptchaApiKey }}"></div>errors["recaptcha"] === "required".