Home
Wiki
Home
Wiki
  1. 2. Components
  • Back to home
  • 1. Themes
  • Vs Code
    • Getting Started
  • Kitchenware
    • Layout
      • New Layout
      • Legacy Layout
    • Components
      • Announcement
      • Banner Carousel
      • Banner With Products Carousel
      • Blog Category List
      • Blog List
      • Brand List
      • Brands Carousel
      • Breadcrumb
      • Call To Action
      • Cart
      • Categories List
      • Change Password
      • Checkout
      • Cookie Manager
      • Filter list
      • Footer
      • Forgot Password
      • Form
      • Hero Carousel
      • Icon Block
      • Invitation
      • Last Visited Products
      • Layout
      • Login
      • Map
      • Nav Bar
      • Offer
      • Product Attachments
      • Product Attributes
      • Product Documentation
      • Product Expected
      • Product Modal
      • Products Block
      • Products Carousel
      • Product Single
      • Profile
      • Quote
      • Register
      • Related Products
      • Search
      • Stores
      • Subscribe Newsletter
      • Text with Image
      • Top Bar
      • Video
    • Reusables
      • Getting Started
    • Assets
      • Getting Started
    • SDK
      • Products
        • _findProductsByCategory
        • _findProductsByIds
        • _findProductsByTitle
        • _findProductsByFilter
        • _findProductsByCriteria
        • _findProductsAndCalculate
        • _findProductsThenCalculate
        • _getProductAttributeSet
        • _setLastVisited
      • Categories
        • _findCategoryTreeById
        • _findCategoriesByIds
        • _findCategoryByAlias
        • _findCategoryTreeByAlias
        • _getCategoryContent
      • Collections
        • _getCollectionContent
        • _findCollectionsByIds
        • _findCollectionsByIdsThenCalculate
      • Brands
        • _getBrandContent
        • _findBrandsByIds
      • Cart
        • _addToCartMulti
        • _addToCart
        • _setCart
        • _clearCart
        • _setCartListener
        • _removeFromCart
        • _calculateCart
      • Checkout
        • _startCheckout
        • _updateCheckout
        • _completeCheckout
      • Shopping Lists
        • _getShoppingLists
        • _updateShoppingList
        • _createShoppingList
        • _deleteShoppingList
        • _getShoppingListByAlias
      • Navigation
        • _getFooterMenu
        • _getHeaderMenu
      • Users
        • _getUserById
      • Utils
        • _calculateCurrency
        • _getCurrencySymbol
        • _getCulture
        • _subscribeToNewsletter
        • _findUnitsByIds
  • Noir
    • 0. Introduction
    • 1. Structure
      • Overview
      • LayoutA.liquid
      • ComponentsList.liquid
      • Metas.liquid
      • CssVariables.liquid
      • Json.liquid
      • GoogleTagManager.liquid
      • StagingButton.liquid
    • 2. Components
      • Overview
      • Announcement
      • BannerCarousel
      • BlogCategoryList
      • BlogList
      • BrandList
      • Breadcrumb
      • Cart
      • CategoriesList
      • ChangePassword
      • Checkout
      • CookieManager
      • FilterList
      • Footer
      • ForgotPassword
      • Form
      • IconBlock
      • Invitation
      • LastVisitedProducts
      • Login
      • Map
      • NavBar
      • ProductAttachments
      • ProductAttributes
      • ProductComparison
      • ProductDocumentation
      • ProductMixList
      • ProductsBlock
      • ProductsCarousel
      • ProductSingle
      • Profile
      • Register
      • RelatedProducts
      • SingleBlog
      • Stores
      • TextWithImage
      • ThankYouPage
      • TopBar
      • Wishlist
    • 3. Reusables
      • Overview
      • Addresses
      • BillingRetail
      • AddressForm
      • AnnouncementModal
      • BackToTop
      • Company
      • General
      • Login
      • LoginModal
      • Orders
      • Payment
      • ProductAttachments
      • ProductAttributes
      • ProductComparisonButton
      • ProductComparisonFloatingButton
      • ProductGridItem
      • ProductListItem
      • ShoppingListsButton
      • ProductModal
      • ProfileInfo
      • PromptModal
      • Register
      • Shipping
      • ShoppingLists
      • ShoppingListsNavbar
      • Toast
      • Users
      • VariantContent
      • WishlistButton
      • Services
    • 4. Assets
      • Fonts
      • Images
      • Templates
      • Javascript
        • Overview
        • theme.js
      • Css / Scss
        • Overview
        • ThemeClasses
    • 5. SDK
      • Overview
      • LiquidGlobals
      • ServicesSDK
  1. 2. Components

Form

Purpose#

The Form component renders a configurable (dynamic) form based on settings.fields[] and submits the collected values through the theme services layer.
Typical use cases:
Contact forms (“Need help?”)
Generic “send email” forms driven by an Email Profile (emailProfileId)
Optional reCAPTCHA gating
It supports:
Multiple field types (Text, Email, TextArea, Radio, Checkbox, Select, File, Consent)
Required field validation + inline field errors
File validation (type + size)
Optional Google reCAPTCHA validation
Toast feedback on submit success/failure

Inputs (model contract)#

Model shape (storefront example)#

{
  "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",
    "...": "..."
  }
}

Required fields#

settings.id
Used for wrapper id: comp-{{ id }}.
settings.fields[]
Defines what the form renders (dynamic fields).
settings.emailProfileId
Sent to the backend to determine which email template/profile is used.

Optional fields#

settings.cssClass
Applied only when non-empty and not (UNDEFINED).
settings.header / settings.description
Rendered as the form title and description.
settings.buttonText
Overrides the submit button text. If missing, falls back to translations.submit.
settings.showReCaptcha (boolean)
If true, renders the reCAPTCHA widget and requires a valid response.
translations.*
Used for the submit button aria-label, inline errors, file validation messages, and toast messages.

JavaScript#

Global object#

The component exposes an Alpine-ready object:

State fields#

isFormValid
Controls if the submit button can be enabled (:disabled="!isFormValid || isSending").
fields
Cached list of all form inputs to validate/serialize (excluding .skipped inputs).
errors
Field-name → error-code mapping (e.g. required, invalidEmail, fileTooLarge, invalidFileType, recaptcha).
isSending
Prevents double submission + toggles submit UI (“Sending…” text + loader).

init()#

What it does
Collects all relevant fields under the form scope:
[name]:not(.skipped)
excludes disabled + submit inputs
Attaches field listeners:
blur for most fields → validates on leaving the field
change for:
select (also toggles placeholder styling)
checkbox, radio, file
Implements group validation for:
Radio groups (.radio-wrap)
Checkbox groups (.checkbox-wrap)
Radio/Checkbox group validation pattern
Each group renders:
real inputs with .skipped (excluded from main fields list)
plus a hidden input name="<groupName>-required" that is marked required
JS keeps the hidden field value updated (selected choice(s)) and validates it.
Advice
This pattern avoids browser-native group quirks and lets the theme show a single “required” error per group.
The focusout + setTimeout(10) logic ensures validation fires when the user tabs out of the whole group.

toggleIsEmptyClass(field)#

What it does
Select-only helper.
Adds/removes isEmpty class on the nearest .select-wrap depending on whether the select is still on the placeholder option.
Advice
This is purely UI styling (placeholder state). Keep it in sync with CSS.

validateField(field)#

What it does
Validates a single field and updates:
this.errors[fieldName]
.valid / .invalid classes on the input or its wrapper
.icon-state icon classes when present
It supports multiple special cases:
1.
Hidden required input inside .checkbox-wrap
Valid when hidden value is non-empty.
Stores error under the group name (base name without -required).
2.
Hidden required input inside .radio-wrap
Valid when a radio is checked.
Stores error under the group name.
3.
Required checkbox group
Valid if at least one checkbox in the group is checked.
Validates only once per group (only when called for the first checkbox).
4.
Required select
Valid when an option other than the placeholder is selected.
5.
Standard fields
Required: non-empty
Email: regex format check (invalidEmail)
File: validates:
allowed types (PNG/JPG/PDF) → invalidFileType
max size 10MB (10485760 bytes) → fileTooLarge
Finally it calls updateFormValidity().
Advice
File type checking uses both MIME type and filename extension fallback—good for browsers that don’t set a reliable MIME.
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.
If you ever enable multi-file uploads (multipleFiles: true), validation currently only checks field.files[0] (first file). You may want to validate all files.

updateFormValidity()#

What it does
Sets isFormValid by ensuring every tracked field:
has no error code in errors
is not empty when required
Advice
This is the single place that decides whether the submit button should be enabled. Keep it called after every validation.

clearForm()#

What it does
Resets the entire form UI and internal state:
Clears text/textarea/email values
Resets selects to placeholder and updates .select-wrap.isEmpty
Clears file inputs
Unchecks checkbox/radio inputs
Resets icons (.icon-state)
Clears wrapper validation classes (.radio-wrap, .checkbox-wrap, .select-wrap)
Resets hidden required inputs (*-required) values
Clears errors and recomputes isFormValid
Advice
This is called on successful submission so the form returns to a pristine state.

getMappedFields(fields)#

What it does
Transforms DOM inputs into the payload structure expected by the backend sendEmail endpoint.
Key behavior:
Maps DOM field type → numeric type codes:
text: 1
textarea: 2
email: 3
file: 4
checkbox: 5
radio: 6
select: 7
consent: 8
Generates entries like:
{ type, name, label, required, value, ... }
Uses getLabelText(field) to extract the human label.
Advice
Only fields not marked .skipped are included.
For group fields, the hidden required inputs carry the actual “selected value(s)” used in the payload.

getLabelText(field)#

What it does
Gets label text reliably from the template:
Finds <label for="<field.id>">
For consent fields:
extracts the <span> text content inside the label (because the label includes HTML)
For standard labels:
extracts only direct text nodes to avoid capturing icon text or nested markup.
Advice
This is important for backend email templates: labels become the “field title” in the email body.
Keep for/id pairs correct for every field, otherwise labels become empty.

checkForm(showReCaptcha, emailProfileId, successMessage, errorMessage) (async)#

What it does
1.
Clears toasts ($store.toast.removeAll())
2.
Validates every field
3.
Stops if !isFormValid
4.
If reCAPTCHA is enabled and grecaptcha exists:
requires grecaptcha.getResponse() !== ""
if empty: marks .g-recaptcha as invalid and sets errors["recaptcha"] = "required"
5.
Sets isSending = true
6.
Builds payload:
{ emailProfileId, fields: getMappedFields(this.fields) }
7.
Calls services layer:
servicesreusabledefault.sendEmail(info)
8.
On failure:
shows sticky error toast (toast.add(..., true))
clears isSending
9.
On success:
clears form
shows success toast
clears isSending
Advice
If showReCaptcha 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.
Consider disabling the button for the entire duration isSending (already implemented via :disabled).

Global Alpine stores (used by Form)#

The Form component uses a global store for feedback and optionally depends on reCAPTCHA globals.

$store.toast (user feedback)#

The Form component uses toasts for submission outcomes.
How Form uses it
Clears any old messages before validation and submit:
Alpine.store("toast").removeAll()
On API failure:
Alpine.store("toast").add(errorMessage, "ic-warning", "error", true)
On success:
Alpine.store("toast").add(successMessage, "ic-check", "success")
What it means in practice
The user gets consistent theme-level feedback (same toast UI used across Cart/Checkout/etc.).
The form itself focuses on inline validation per field; submission outcome is communicated via toast.
Notes
The form also writes errors["recaptcha"] for inline reCAPTCHA required messaging, but that’s local state (not a global store).
reCAPTCHA is not an Alpine store; it’s a global grecaptcha. Still, it’s an important cross-cutting dependency when enabled.

Services / API calls#

servicesreusabledefault.sendEmail({ emailProfileId, fields })
Sends the form payload to the configured email profile.

Dependencies#

Alpine.js
$store.toast
servicesreusabledefault.sendEmail(...)
Optional: Google reCAPTCHA:
grecaptcha global
GlobalData.Settings.reCaptchaApiKey

Notes#

Field IDs are built using {field.name}-{index}-{componentId} to keep them unique across the page.
Checkbox/Radio required validation is implemented via hidden required inputs, not by relying only on the native group required behavior.
File uploads are validated client-side against JPG/PNG/PDF and max 10MB, but backend should still validate again for security.

Extras#

Template behavior (Liquid + Alpine)#

Initialization#

The Alpine component is mounted with:
x-data="formdefault"
The submit handler calls:
checkForm(showReCaptcha, emailProfileId, successMessage, errorMessage)

Dynamic field rendering#

The template loops settings.fields[] and renders different markup per field.type, including:
Text, Email, TextArea inputs with per-field inline required/email errors
File input with file validation errors
Checkbox/Radio groups using a “hidden required input” pattern (explained below)
Select with a placeholder option and wrapper styling (.select-wrap)
Consent checkbox (.consent wrapper)

Required fields notice#

The template detects whether any field is required (hasRequiredField)
If true, it prints translations.requiredFieldsNotice

reCAPTCHA (optional)#

Rendered when settings.showReCaptcha is true:
<div class="g-recaptcha" data-sitekey="{{ GlobalData.Settings.reCaptchaApiKey }}"></div>
Inline error is driven by errors["recaptcha"] === "required".
Modified at 2026-04-14 13:18:56
Previous
ForgotPassword
Next
IconBlock
Built with