Component Contract

Forms

Unified form component for contact, enrollment, feedback, and general data collection across CAAT digital properties.

Live Demo

Variant 1 — Contact Form

Contact Member Services

As it appears on your pension statement.
We'll respond to this address within 2 business days.

Variant 2 — Enrollment / Registration (Multi-Step)

New Member Enrollment

  1. Personal Info
  2. Employment
  3. Beneficiary
Step 1: Personal Information
Format: 000-000-000. Required for pension administration.
Step 2: Employment Details
Step 3: Beneficiary Information

Variant 3 — Simple Feedback Form

How was your experience?

Overall Rating required

States

Follows the Interaction States matrix. Inputs implement default, focus, valid, invalid (error), and disabled. The static is-focus class mirrors the live :focus ring for documentation and Figma export.

Looks good.
Enter a valid work email address, such as name@organization.ca.
This field is required.

Variant 4 — Pension Solutions Form (Account Engagement)

Use this pattern for employer-facing lead-gen forms posted to Account Engagement (Pardot). Includes logo, UTM tracking, honeypot, reCAPTCHA, email opt-in, and privacy consent.

CAAT Pension Plan

Connect with Pension Solutions

Complete the form below and our team will follow up within 2 business days.

By submitting this form, you agree that CAAT may use the information provided to respond to your inquiry and follow up about pension solutions. Personal information is handled according to CAAT's privacy practices.

reCAPTCHA

Form Consent, Validation & Follow-Up Patterns

Standard patterns for Account Engagement forms, campaign landing pages, gated content, event registrations, and contact requests.

Consent Language

Place consent near the submit button, not hidden in a footer. Use plain language that explains why CAAT is collecting information and what follow-up may happen.

By submitting this form, you agree that CAAT may use the information provided to respond to your inquiry and follow up about pension solutions. Personal information is handled according to CAAT's privacy practices.
Email Opt-In

Use an unchecked checkbox for marketing email permission. Do not bundle email consent into the form submission itself.

Required Fields
  • Mark required fields with * in the label and expose "required" to screen readers via .visually-hidden.
  • Add Province/territory when routing, segmentation, events, or regional follow-up matters.
  • Keep phone optional unless a phone call is essential to the service request.
  • Add aria-required="true" to the input, not just the HTML required attribute.
Validation Copy Patterns

Be specific — tell the user what format is expected, not just "invalid input."

Enter a valid work email address, such as name@organization.ca.
Province selected.

Thank You Page Pattern

A successful form submission should redirect to a dedicated thank-you page or show a persistent success state. Never leave users on a blank form or rely only on a toast notification.

Thank you!

A member of the Pension Solutions team will be in touch with you within 2 business days.

Return to Pension Solutions
Thank-you page requirements:
  • Confirm the form was received.
  • State who will follow up and when, if known.
  • Offer a relevant next step (resource link, event registration, etc.).
  • Do not repeat personal information back to the user.
  • Fire a form-submit-success analytics event on page load.
Thank-you page starter HTML
<main class="caat-form__thank-you">
  <div class="caat-form__thank-you-icon">
    <i class="bi bi-check-circle-fill"></i>
  </div>
  <h1>Thank you!</h1>
  <p>A member of the Pension Solutions team
  will be in touch with you soon.</p>
  <a href="/pension-solutions"
     class="caat-button caat-button--primary">
    Return to Pension Solutions
  </a>
</main>

1. Summary

Component namecaat/components/form
ConsolidatesContact Us (#26), Generic Form (#38), Core Form (#31)
AEM TriageRebuild
PurposeUnified form component for contact, enrollment, feedback, and general data collection
VariantsSingle-step, Multi-step (with progress indicator)
Field typestext, email, tel, select, textarea, checkbox, radio, date, file

2. Design Tokens

Forms consume these semantic tokens — no component-specific tokens are defined.

TokenRole
--caat-color-borderInput / select / textarea border
--caat-color-focus-ringFocus ring
--caat-color-state-invalidInvalid (error) border + message colour
--caat-greenValid state accent
--caat-color-state-selected-bg / -selected-textChecked radio / checkbox fill + glyph
--caat-color-state-disabled-bgDisabled control surface
--caat-color-text-headingField label colour
--caat-color-text-mutedHelp / hint text
--caat-color-text-linkIn-field links
--caat-color-bg-pageInput background
--caat-radiusControl border-radius
--caat-font-primaryForm font family

3. Authoring Fields

FieldTypeRequiredNotes
Form Action URLPath/URLYesEndpoint that receives the submitted data
MethodDropdown (GET / POST)YesHTTP method; defaults to POST
Form TitleTextNoRendered as <h4> inside the form
Success MessageRich textNoDisplayed on successful submission
Enable Multi-StepToggleNoEnables step-based layout with progress indicator
Fields (Multifield)
Field TypeDropdownYestext | email | tel | select | textarea | checkbox | radio | date | file
LabelTextYesVisible label for the field
PlaceholderTextNoPlaceholder text for inputs/textarea
RequiredToggleNoMarks field as required
Validation PatternText (regex)NoCustom regex pattern for validation
Help TextTextNoDescriptive text below the input
Options ListMultifield (label/value)ConditionalRequired for select / radio field types
Step GroupNumberConditionalWhich step this field belongs to (multi-step only)

4. Validation Rules

RuleBehaviour
Action URL must be populatedDialog prevents save without a valid form action
At least one field must existForm is not rendered if the fields multifield is empty
Options required for select/radioDialog validation error when field type is select or radio and options list is empty
Email fields validate formatClient-side regex + type="email" ensures valid email format
Tel fields validate formatPattern attribute enforces phone number format
Required fields must have contentClient-side required attribute + aria-required
Multi-step validates per stepEach step must be valid before progressing; focus moves to first error

Behaviour contract (JS)

The design system ships no forms JavaScript — behaviour below is the implementing team's responsibility (AEM clientlib). The live demo includes a minimal reference implementation (goToStep() in this page's source) for the step-switching mechanics only.

BehaviourRequired implementation
Step switchingSteps are sibling <fieldset class="caat-form__step"> elements; exactly one visible at a time (others .d-none). On change: update progress <li> classes (.completed for past, .active + aria-current="step" for current), then move focus to the first field of the revealed step.
Advance gatingBefore advancing, run constraint validation on the current step only (fieldset.querySelectorAll(':invalid') or equivalent). If invalid: stay on step, render errors, focus the first invalid field. The demo's goToStep() deliberately omits gating — do not ship it as-is.
Back navigation"Back" never validates — users may always return to a previous step with data preserved (steps are hidden, not unmounted).
Error renderingAdd .is-invalid to the control, set aria-invalid="true", insert/reveal the message element referenced by the control's aria-describedby, with role="alert".
Submit / pendingOn submit: disable the submit button using the Button loading pattern (aria-busy="true", see Button contract §States) to prevent double submission; re-enable on failure with a role="alert" summary above the form.
SuccessReplace the form with the authored success message (or redirect to the Thank-You page pattern); move focus to the success heading.

5. Content Guidance

  • Labels: Use clear, concise labels. Avoid jargon — say "Date of Birth" not "DOB."
  • Placeholder text: Show an example value (e.g., "jane.smith@example.com") rather than repeating the label.
  • Help text: Explain why the information is needed or the expected format (e.g., "Format: 000-000-000").
  • Error messages: Be specific and actionable — "Please enter a valid email address" not "Invalid input."
  • Submit button: Use a verb phrase that describes the action — "Submit Enrollment," "Send Message."
  • Progress steps: Use short labels (2–3 words) for multi-step indicators.
  • Privacy note: For forms collecting personal data, include a link to CAAT's Privacy Policy.

6. Semantic HTML

<form class="caat-form" action="/api/contact" method="POST" novalidate
      aria-label="Contact Member Services"
      data-analytics-component="form"
      data-analytics-form-name="contact-member-services">

  <div class="caat-form__group">
    <label class="form-label" for="field-name">
      Full Name <span class="text-danger" aria-hidden="true">*</span>
    </label>
    <input class="form-control" type="text" id="field-name"
           name="name" required aria-required="true"
           aria-describedby="field-name-help field-name-error">
    <span class="caat-form__help" id="field-name-help">
      As it appears on your pension statement.
    </span>
    <span class="caat-form__error" id="field-name-error" role="alert"
          hidden>
      Please enter your full name.
    </span>
  </div>

  <!-- Grouped fields (radio/checkbox) -->
  <fieldset class="caat-form__group">
    <legend class="form-label">Preferred Contact Method</legend>
    <label class="caat-form__radio">
      <input type="radio" name="contactMethod" value="email"> Email
    </label>
    <label class="caat-form__radio">
      <input type="radio" name="contactMethod" value="phone"> Phone
    </label>
  </fieldset>

  <button type="submit" class="caat-button caat-button--primary">
    Submit Inquiry
  </button>
</form>

Multi-Step Structure

<form class="caat-form caat-form--multi-step" …>
  <div class="caat-form__progress" aria-label="Form progress">
    <ol class="caat-form__progress-steps">
      <li class="caat-form__progress-step active" aria-current="step">
        <span class="step-number">1</span> Personal Info
      </li>
      <li class="caat-form__progress-step">…</li>
    </ol>
  </div>

  <fieldset class="caat-form__step" id="step-1">
    <legend class="visually-hidden">Step 1: Personal Information</legend>
    <!-- fields -->
  </fieldset>
</form>

7. CSS Contract

ClassElementNotes
.caat-formForm rootWrapper for all form variants
.caat-form--multi-stepForm root modifierEnables step-based layout
.caat-form__groupField containerWraps label + input + help/error
.caat-form__labelLabelFont-weight via token
.caat-form__inputText/email/tel/date/file inputsShared styling for single-line inputs
.caat-form__selectSelect dropdownCustom arrow indicator
.caat-form__textareaMulti-line textResizable vertically only
.caat-form__checkboxCheckbox wrapperCustom checkbox styling
.caat-form__radioRadio wrapperCustom radio styling
.caat-form__helpHelp textBelow the input; uses help-color token
.caat-form__errorError messageRed text with icon; hidden until invalid
.caat-form__progressProgress containerMulti-step progress bar
.caat-form__progress-stepsOrdered list of stepsHorizontal step indicators
.caat-form__progress-stepIndividual step.active for current step
.is-validState modifierGreen border for validated fields
.is-invalidState modifierRed border + shows error message

8. Accessibility

RequirementImplementation
Labels linked to inputsEvery <input> / <select> / <textarea> has a matching <label for="…">
Help & error descriptionsaria-describedby references both help text and error message IDs
Required indicatoraria-required="true" on required fields; visual asterisk is aria-hidden
Invalid statearia-invalid="true" set dynamically when validation fails
Error announcementsError messages use role="alert" so screen readers announce immediately
Focus managementOn validation failure, focus moves to first invalid field; on step change, focus moves to first field in new step
Grouped controlsRadio groups and checkbox groups wrapped in <fieldset> with <legend>
Progress indicatoraria-current="step" on active step; aria-label on progress container
Colour contrastAll form text, borders, and error states meet WCAG 2.1 AA (4.5:1 for text)
Touch targetsMinimum 44×44px tap area for checkboxes, radios, and buttons

9. SEO

Transient / structural UI with no indexable content of its own — no SEO markup required. Ensure any content this component wraps or reveals stays server-rendered and crawlable.

10. Analytics

Analytics are captured through the shared data-analytics-component attribute on the component root (emitted automatically) and reported to the central data layer. Event names and payloads follow the organisation's standard analytics schema — defined centrally with the analytics team, not invented per component — so no bespoke tracking is specified here.

11. AEM Implementation Notes

  • Resource type: caat/components/form
  • Proxy: /apps/caat/components/form
  • Sling model: Provides field list, step configuration, action URL, and method.
  • Client-side validation: Progressive enhancement — uses novalidate on form, JS handles validation and focus management.
  • Server-side handling: Action URL should point to a Sling POST servlet or external API endpoint.
  • Multi-step JS: A lightweight controller manages step visibility, progress state, and per-step validation.
  • File uploads: When file field type is used, form encoding switches to multipart/form-data.
  • CSRF protection: Include AEM CSRF token as hidden field for POST submissions.
  • Captcha: Optional reCAPTCHA/hCaptcha integration configured at the form level.

12. Dialog Model

Simplified model — shows field intent and Granite UI resource types, not the literal cq:dialog node tree (e.g. select options are authored as child items nodes).

{
  "jcr:primaryType": "nt:unstructured",
  "sling:resourceType": "cq/gui/components/authoring/dialog",
  "content": {
    "items": {
      "tabs": {
        "items": {
          "formSettings": {
            "jcr:title": "Form Settings",
            "items": {
              "columns": {
                "items": {
                  "column": {
                    "items": {
                      "formTitle": { "sling:resourceType": "granite/ui/components/coral/foundation/form/textfield", "name": "./formTitle", "fieldLabel": "Form Title" },
                      "actionUrl": { "sling:resourceType": "granite/ui/components/coral/foundation/form/textfield", "name": "./actionUrl", "fieldLabel": "Action URL", "required": true },
                      "method": { "sling:resourceType": "granite/ui/components/coral/foundation/form/select", "name": "./method", "fieldLabel": "Method", "items": { "post": { "text": "POST", "value": "POST", "selected": true }, "get": { "text": "GET", "value": "GET" } } },
                      "enableMultiStep": { "sling:resourceType": "granite/ui/components/coral/foundation/form/checkbox", "name": "./enableMultiStep", "text": "Enable Multi-Step Layout" },
                      "successMessage": { "sling:resourceType": "granite/ui/components/coral/foundation/form/textarea", "name": "./successMessage", "fieldLabel": "Success Message" }
                    }
                  }
                }
              }
            }
          },
          "fields": {
            "jcr:title": "Form Fields",
            "items": {
              "columns": {
                "items": {
                  "column": {
                    "items": {
                      "fields": {
                        "sling:resourceType": "granite/ui/components/coral/foundation/form/multifield",
                        "fieldLabel": "Fields",
                        "composite": true,
                        "field": {
                          "items": {
                            "fieldType": { "sling:resourceType": "granite/ui/components/coral/foundation/form/select", "name": "./fieldType", "fieldLabel": "Field Type", "items": { "text": { "text": "Text", "value": "text" }, "email": { "text": "Email", "value": "email" }, "tel": { "text": "Telephone", "value": "tel" }, "select": { "text": "Dropdown", "value": "select" }, "textarea": { "text": "Textarea", "value": "textarea" }, "checkbox": { "text": "Checkbox", "value": "checkbox" }, "radio": { "text": "Radio Buttons", "value": "radio" }, "date": { "text": "Date", "value": "date" }, "file": { "text": "File Upload", "value": "file" } } },
                            "label": { "sling:resourceType": "granite/ui/components/coral/foundation/form/textfield", "name": "./label", "fieldLabel": "Label", "required": true },
                            "placeholder": { "sling:resourceType": "granite/ui/components/coral/foundation/form/textfield", "name": "./placeholder", "fieldLabel": "Placeholder" },
                            "required": { "sling:resourceType": "granite/ui/components/coral/foundation/form/checkbox", "name": "./required", "text": "Required" },
                            "validationPattern": { "sling:resourceType": "granite/ui/components/coral/foundation/form/textfield", "name": "./validationPattern", "fieldLabel": "Validation Pattern (regex)" },
                            "helpText": { "sling:resourceType": "granite/ui/components/coral/foundation/form/textfield", "name": "./helpText", "fieldLabel": "Help Text" },
                            "options": { "sling:resourceType": "granite/ui/components/coral/foundation/form/multifield", "name": "./options", "fieldLabel": "Options (for select/radio)", "composite": true }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

13. QA Acceptance Checklist

  • All field types render correctly (text, email, tel, select, textarea, checkbox, radio, date, file).
  • Required fields show asterisk and aria-required="true".
  • Validation triggers on submit (not on blur for first interaction).
  • Invalid fields show error message with role="alert" and receive focus.
  • Valid fields show green border confirmation.
  • Multi-step progress indicator updates correctly and announces step changes.
  • Each step validates before allowing progression.
  • Back button in multi-step preserves entered data.
  • Labels are correctly associated (for / id matching).
  • aria-describedby links help text and error messages to their fields.
  • Radio/checkbox groups use fieldset and legend.
  • Form is fully operable via keyboard only.
  • Analytics events fire: form-start, form-step, form-submit, form-error.
  • Responsive layout renders correctly on mobile, tablet, and desktop.
  • File upload field accepts specified file types and shows file name.

14. Definition of Done

AreaDone means
Design Figma component matches this contract. All field types, validation states, and multi-step progress are documented. Tokens are used — no hard-coded colour values.
Development AEM proxy component created. HTL template emits semantic HTML per Section 6. CSS uses design tokens. JS handles validation, focus management, multi-step navigation, and analytics events. Unit tests pass.
QA All items in the QA Acceptance Checklist (Section 13) pass. Manual accessibility audit completed. Cross-browser tested (Chrome, Firefox, Safari, Edge). Screen reader tested (NVDA, VoiceOver).
Launch Component deployed to AEM production. Content authors trained on multifield configuration. Documentation published. Analytics dashboards confirmed receiving all form events.