Brand Applications

Lead Forms.

Every Men's Wellness Centers lead form follows one pattern. Dark navy surface, crisp white input fields, a single glowing orange CTA with an arrow, mobile-first sizing, and descriptive error handling that respects the person submitting it. Anything else is off-brand and converts worse.

Live Example

The official lead form pattern.

This is a working form. Try submitting it empty or with invalid data to see how error states feel. Every landing page, GHL widget, ad funnel, and WordPress lead form must match this pattern.

Start feeling like yourself again.

No-cost 60-minute in-person appointment. Same-day labs. No insurance needed.

Choose your nearest center.

Got it. We will call you shortly.

A Men's Wellness Centers team member will call you within one business hour to schedule your 60-minute in-person appointment.

Do / Don't

The pattern, side by side.

Left: the approved 2026 pattern. Right: the retired WordPress form on menswellnesscenters.com/book-a-consultation. It is being replaced. Do not copy it.

Do · 2026 Pattern
  • Midnight Navy form surface (#0B1029)
  • Pure white input fields (#FFFFFF)
  • Uppercase eyebrow labels above each field
  • Lefthand input icons (user, phone, pin)
  • Single glowing orange CTA with arrow
  • Mobile-first; 16px input font (no iOS zoom)
  • Descriptive field-level error copy
  • Above-form error banner for submit failures
  • Loading spinner, then success state in place
  • Says "appointment", never "consult" or "visit"
Don't · Retired WordPress
  • Cream/beige fill inside fields
  • Hairline gray borders that disappear on mobile
  • Pale placeholder-only labels (low contrast)
  • Flat orange button with no glow, no arrow
  • Light theme that fades the form into the page
  • "Book a No-Cost Appointment" headline paired with a URL that still says "consult"
  • No inline validation, no descriptive errors
  • Generic submit failures with no recovery path

Field States

Every state, defined.

Each field has five states. Build all five every time; do not ship a form that only handles the default state.

StateVisualWhen
DefaultWhite field, 2px transparent border, gray placeholderPage load, before user interaction
Focus2px orange border + 4px orange glow ring (rgba 232,103,10,0.22)User tabs or taps into the field
ValidWhite field, green checkmark on the rightUser blurs the field with a valid value
Error2px red border, red icon, descriptive message belowUser blurs with an invalid value, or submit fails validation
DisabledGray field, no glow, cursor: not-allowedOnly on the submit button while submitting

Error Handling

Descriptive errors that respect the user.

Never say "Invalid" or "Required". Tell the user exactly what is wrong and exactly how to fix it. Field-level errors appear under the field. Submit-level errors appear in a banner at the top.

TriggerError copy (use verbatim)
Name emptyPlease enter your name so we can address you correctly.
Name too shortName must be at least 2 characters.
Name has invalid charactersPlease use only letters, spaces, hyphens, and apostrophes.
Phone emptyPlease enter your phone number. We will call you within one business hour.
Phone too shortPhone number is too short. Please include the area code.
Phone not USPhone number must be a valid US number.
Email invalidThat email does not look right. Please check and try again.
Location missingPlease select the center closest to you.
Consent uncheckedPlease agree to receive messages so we can confirm your appointment.
Submit failed (network)We could not submit your request. Please check your connection and try again, or call us directly at (804) 346-4636.

Mobile

Mobile-first, mobile-perfect.

Most lead traffic is paid-media mobile. Test every form at 390px width first.

SpecValueWhy
Input font-size16px minimumiOS Safari zooms when smaller than 16px. Always 16px+.
Tap target height48px minimumWCAG and platform guidelines. Use 56-60px for primary CTAs.
Submit width100% on mobileFull-width buttons are easier to thumb-tap.
Vertical spacing18px between fieldsFat-finger insurance.
Phone keyboardinputmode="tel"Pulls up the number pad on iOS and Android.
Autocompleteautocomplete="name", "tel", "email"Lets browsers and password managers prefill.
Form padding24px x 18px on mobile (was 36 x 32)More content room without losing breathing space.

Accessibility

Required a11y rules.

  • Every input has a real <label>, not just a placeholder. Placeholders disappear on focus.
  • Required fields use the required attribute and the validator is in JS, not native browser tooltips.
  • Error messages use role="alert" and are linked via aria-describedby.
  • Focus state is a 4px orange ring with 14.7:1 contrast against the navy surface.
  • Submit button gets aria-busy="true" while submitting.
  • Success state has role="status" and aria-live="polite".
  • Form respects prefers-reduced-motion and prefers-contrast: more.

Copy / Paste Kit

Three files. Drop them in.

Copy the HTML, CSS, and JS into any landing page, GHL HTML block, or WordPress Custom HTML widget. Class names are prefixed with .mwc- so they will not collide with host themes.

Download Lead Form Kit (.zip)
<!-- ========================================================= MWC Lead Form Pattern v2026.06 Drop into any landing page, GHL HTML block, or WordPress Custom HTML widget. Pair with mwc-leadform.css and mwc-leadform.js. Replace ACTION_URL with your endpoint. ========================================================= --> <form class="mwc-form" id="mwc-leadform" novalidate aria-labelledby="mwc-form-headline"> <div class="mwc-form__body"> <h2 class="mwc-form__headline" id="mwc-form-headline">Start feeling like yourself again.</h2> <p class="mwc-form__sub">No-cost 60-minute in-person appointment. Same-day labs. No insurance needed.</p> <!-- Form-wide error banner --> <div class="mwc-form__error-banner" role="alert" aria-live="polite" id="mwc-form-banner"> Please correct the highlighted fields and try again. </div> <!-- NAME --> <div class="mwc-field" data-field="name"> <label class="mwc-field__label" for="mwc-name">Name</label> <svg class="mwc-field__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg> <input class="mwc-input" type="text" id="mwc-name" name="name" autocomplete="name" required minlength="2" placeholder="Your full name" aria-describedby="err-name"> <svg class="mwc-field__status mwc-field__status--ok" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg> <svg class="mwc-field__status mwc-field__status--err" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg> <p class="mwc-field__error" id="err-name" role="alert"></p> </div> <!-- PHONE --> <div class="mwc-field" data-field="phone"> <label class="mwc-field__label" for="mwc-phone">Phone</label> <svg class="mwc-field__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.01 15.38c-1.23 0-2.42-.2-3.53-.56a.977.977 0 00-1.01.24l-1.57 1.97c-2.83-1.35-5.48-3.9-6.89-6.83l1.95-1.66c.27-.28.35-.67.24-1.02-.37-1.11-.56-2.3-.56-3.53 0-.54-.45-.99-.99-.99H4.19C3.65 3 3 3.24 3 3.99 3 13.28 10.73 21 20.01 21c.71 0 .99-.63.99-1.18v-3.45c0-.54-.45-.99-.99-.99z"/></svg> <input class="mwc-input" type="tel" id="mwc-phone" name="phone" autocomplete="tel" inputmode="tel" required placeholder="(555) 555-5555" aria-describedby="err-phone"> <svg class="mwc-field__status mwc-field__status--ok" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg> <svg class="mwc-field__status mwc-field__status--err" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg> <p class="mwc-field__error" id="err-phone" role="alert"></p> </div> <!-- LOCATIONS --> <div class="mwc-locations" role="radiogroup" aria-labelledby="loc-label" data-field="location"> <p class="mwc-locations__label" id="loc-label">Choose your nearest center.</p> <label class="mwc-loc"> <span class="mwc-loc__left"> <svg class="mwc-loc__pin" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 010-5 2.5 2.5 0 010 5z"/></svg> Glen Allen </span> <span class="mwc-loc__check"></span> <input type="radio" name="location" value="glen-allen" required> </label> <label class="mwc-loc"> <span class="mwc-loc__left"> <svg class="mwc-loc__pin" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 010-5 2.5 2.5 0 010 5z"/></svg> Richmond </span> <span class="mwc-loc__check"></span> <input type="radio" name="location" value="richmond" required> </label> <label class="mwc-loc"> <span class="mwc-loc__left"> <svg class="mwc-loc__pin" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 010-5 2.5 2.5 0 010 5z"/></svg> Mechanicsville </span> <span class="mwc-loc__check"></span> <input type="radio" name="location" value="mechanicsville" required> </label> <label class="mwc-loc"> <span class="mwc-loc__left"> <svg class="mwc-loc__pin" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 010-5 2.5 2.5 0 010 5z"/></svg> Newport News </span> <span class="mwc-loc__check"></span> <input type="radio" name="location" value="newport-news" required> </label> <label class="mwc-loc"> <span class="mwc-loc__left"> <svg class="mwc-loc__pin" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 010-5 2.5 2.5 0 010 5z"/></svg> Virginia Beach </span> <span class="mwc-loc__check"></span> <input type="radio" name="location" value="virginia-beach" required> </label> <p class="mwc-field__error" id="err-location" role="alert"></p> </div> <!-- CONSENT --> <label class="mwc-consent"> <input type="checkbox" name="consent" required> <span>I agree to receive SMS/calls and texts from Men's Wellness Centers. Msg &amp; data rates may apply. Reply STOP to opt out. Not a condition of service. HIPAA compliant. <a href="https://menswellnesscenters.com/privacy-policy/" target="_blank" rel="noopener">Privacy Policy</a></span> </label> <!-- SUBMIT --> <button type="submit" class="mwc-submit"> <span class="mwc-submit__text">Book My Appointment</span> <svg class="mwc-submit__arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> </button> </div> <!-- SUCCESS --> <div class="mwc-form__success" role="status" aria-live="polite"> <div class="mwc-form__success-icon"> <svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg> </div> <h3 class="mwc-form__success-title">Got it. We will call you shortly.</h3> <p class="mwc-form__success-body">A Men's Wellness Centers team member will call you within one business hour to schedule your 60-minute in-person appointment.</p> </div> </form>
/* ========================================================= MWC Lead Form Pattern v2026.06 Copy this whole file into any landing page, GHL form HTML block, or WordPress page. Class names are namespaced with .mwc- so they will not collide with host theme styles. ========================================================= */ /* ---- Container ---------------------------------------- */ .mwc-form { background: #0B1029; color: #F5F3F0; border-radius: 16px; padding: 36px 32px; box-shadow: 0 24px 60px rgba(0,0,0,0.45); font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; max-width: 520px; margin: 0 auto; box-sizing: border-box; } .mwc-form *, .mwc-form *::before, .mwc-form *::after { box-sizing: border-box; } /* ---- Headline + subhead ------------------------------ */ .mwc-form__headline { font-family: 'Oswald', Impact, sans-serif; font-weight: 700; font-size: clamp(22px, 5vw, 28px); text-transform: uppercase; letter-spacing: 0.02em; line-height: 1.15; color: #FFFFFF; margin: 0 0 10px; } .mwc-form__sub { font-size: 14px; line-height: 1.5; color: #B0ADA8; margin: 0 0 24px; } /* ---- Field group ------------------------------------- */ .mwc-field { margin-bottom: 18px; position: relative; } .mwc-field__label { display: block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.16em; color: #4F555C; margin-bottom: 8px; } /* ---- Input + select ---------------------------------- */ .mwc-input, .mwc-select { width: 100%; background: #FFFFFF; color: #0B1029; border: 2px solid transparent; border-radius: 10px; font-family: inherit; font-size: 16px; /* iOS will not zoom on focus when >= 16 */ font-weight: 500; padding: 14px 16px 14px 46px; line-height: 1.3; -webkit-appearance: none; appearance: none; transition: border-color 140ms ease, box-shadow 140ms ease; } .mwc-input:focus, .mwc-select:focus { outline: none; border-color: #E8670A; box-shadow: 0 0 0 4px rgba(232,103,10,0.22); } .mwc-input::placeholder { color: #4F555C; } /* ---- Field icon (left) ------------------------------- */ .mwc-field__icon { position: absolute; left: 16px; bottom: 14px; width: 22px; height: 22px; color: #4F555C; pointer-events: none; } /* ---- Validation status (right) ----------------------- */ .mwc-field__status { position: absolute; right: 16px; bottom: 14px; width: 22px; height: 22px; display: none; pointer-events: none; } .mwc-field.is-valid .mwc-field__status--ok { display: block; color: #10B981; } .mwc-field.is-error .mwc-field__status--err { display: block; color: #EF4444; } /* ---- Error message ----------------------------------- */ .mwc-field__error { display: none; margin-top: 8px; font-size: 13px; line-height: 1.4; color: #FCA5A5; padding-left: 26px; background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23EF4444'><path d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z'/></svg>") left center no-repeat; background-size: 18px 18px; } .mwc-field.is-error .mwc-input, .mwc-field.is-error .mwc-select { border-color: #EF4444; } .mwc-field.is-error .mwc-field__error { display: block; } /* ---- Center selector (radio chips) ------------------- */ .mwc-locations { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; } .mwc-locations__label { font-size: 13px; color: #B0ADA8; margin-bottom: 4px; } .mwc-loc { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border: 2px solid #1E244A; background: #161B3A; border-radius: 10px; cursor: pointer; transition: border-color 140ms ease, background 140ms ease; } .mwc-loc input[type="radio"] { position: absolute; opacity: 0; pointer-events: none; } .mwc-loc__left { display: flex; align-items: center; gap: 12px; color: #FFFFFF; font-weight: 600; font-size: 15px; } .mwc-loc__pin { width: 20px; height: 20px; color: #FFFFFF; } .mwc-loc__check { width: 22px; height: 22px; border-radius: 50%; border: 2px solid #4A5070; position: relative; flex-shrink: 0; } .mwc-loc:has(input:checked), .mwc-loc.is-selected { border-color: #E8670A; background: rgba(232,103,10,0.10); } .mwc-loc:has(input:checked) .mwc-loc__pin, .mwc-loc.is-selected .mwc-loc__pin { color: #E8670A; } .mwc-loc:has(input:checked) .mwc-loc__check, .mwc-loc.is-selected .mwc-loc__check { background: #E8670A; border-color: #E8670A; } .mwc-loc:has(input:checked) .mwc-loc__check::after, .mwc-loc.is-selected .mwc-loc__check::after { content: ""; position: absolute; inset: 4px; background: #FFFFFF; border-radius: 50%; } /* ---- Consent block ----------------------------------- */ .mwc-consent { display: flex; gap: 12px; align-items: flex-start; margin: 6px 0 24px; font-size: 13px; line-height: 1.5; color: #B0ADA8; } .mwc-consent input[type="checkbox"] { appearance: none; -webkit-appearance: none; width: 22px; height: 22px; flex-shrink: 0; margin-top: 1px; border: 2px solid #4A5070; border-radius: 4px; background: #161B3A; cursor: pointer; position: relative; transition: background 140ms ease, border-color 140ms ease; } .mwc-consent input[type="checkbox"]:checked { background: #E8670A; border-color: #E8670A; } .mwc-consent input[type="checkbox"]:checked::after { content: ""; position: absolute; left: 5px; top: 1px; width: 6px; height: 12px; border: solid #FFFFFF; border-width: 0 2px 2px 0; transform: rotate(45deg); } .mwc-consent a { color: #E8670A; text-decoration: none; } .mwc-consent a:hover { text-decoration: underline; } /* ---- Primary submit button (glow + arrow) ------------ */ .mwc-submit { width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 12px; background: #E8670A; color: #FFFFFF; font-family: inherit; font-size: 16px; font-weight: 700; letter-spacing: 0.02em; border: none; border-radius: 10px; padding: 18px 24px; cursor: pointer; box-shadow: 0 0 0 0 rgba(232,103,10,0.0), 0 12px 32px rgba(232,103,10,0.35), 0 0 28px rgba(232,103,10,0.45); transition: box-shadow 200ms ease, transform 100ms ease, background 140ms ease; } .mwc-submit:hover { background: #E8670A; box-shadow: 0 16px 40px rgba(232,103,10,0.50), 0 0 44px rgba(232,103,10,0.65); } .mwc-submit:active { transform: translateY(1px); } .mwc-submit:focus-visible { outline: none; box-shadow: 0 0 0 4px #FFFFFF, 0 0 0 8px #E8670A; } .mwc-submit[disabled] { background: #4A5070; color: #4F555C; cursor: not-allowed; box-shadow: none; } .mwc-submit__arrow { width: 18px; height: 18px; transition: transform 200ms ease; } .mwc-submit:hover .mwc-submit__arrow { transform: translateX(4px); } /* ---- Form-wide error banner -------------------------- */ .mwc-form__error-banner { display: none; margin: 0 0 16px; padding: 12px 14px 12px 42px; background: rgba(239,68,68,0.12); border: 1px solid rgba(239,68,68,0.35); border-radius: 8px; color: #FCA5A5; font-size: 13px; line-height: 1.45; position: relative; } .mwc-form.has-error .mwc-form__error-banner { display: block; } .mwc-form__error-banner::before { content: ""; position: absolute; left: 12px; top: 12px; width: 22px; height: 22px; background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23EF4444'><path d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z'/></svg>") center/22px no-repeat; } /* ---- Success state ----------------------------------- */ .mwc-form__success { display: none; padding: 12px 0; text-align: center; } .mwc-form.is-success .mwc-form__success { display: block; } .mwc-form.is-success .mwc-form__body { display: none; } .mwc-form__success-icon { width: 56px; height: 56px; margin: 0 auto 16px; border-radius: 50%; background: rgba(16,185,129,0.12); display: flex; align-items: center; justify-content: center; color: #10B981; } .mwc-form__success-title { font-family: 'Oswald', sans-serif; font-size: 22px; font-weight: 700; color: #FFFFFF; text-transform: uppercase; letter-spacing: 0.02em; margin: 0 0 8px; } .mwc-form__success-body { font-size: 14px; color: #B0ADA8; line-height: 1.5; } /* ---- Loading state ----------------------------------- */ .mwc-form.is-submitting .mwc-submit { color: transparent; pointer-events: none; } .mwc-form.is-submitting .mwc-submit::after { content: ""; position: absolute; width: 18px; height: 18px; border: 2px solid rgba(255,255,255,0.4); border-top-color: #FFFFFF; border-radius: 50%; animation: mwc-spin 700ms linear infinite; } @keyframes mwc-spin { to { transform: rotate(360deg); } } .mwc-submit { position: relative; } /* ---- Mobile breakpoints ------------------------------ */ @media (max-width: 480px) { .mwc-form { padding: 24px 18px; border-radius: 12px; } .mwc-form__headline { font-size: 22px; } .mwc-input, .mwc-select { padding: 12px 14px 12px 42px; font-size: 16px; } .mwc-field__icon { left: 14px; bottom: 12px; } .mwc-field__status { right: 14px; bottom: 12px; } .mwc-submit { padding: 16px 20px; font-size: 15px; } } /* ---- Reduced motion / contrast ----------------------- */ @media (prefers-reduced-motion: reduce) { .mwc-input, .mwc-select, .mwc-submit, .mwc-submit__arrow, .mwc-loc, .mwc-consent input[type="checkbox"] { transition: none; } .mwc-form.is-submitting .mwc-submit::after { animation: none; } } @media (prefers-contrast: more) { .mwc-input, .mwc-select { border-color: #FFFFFF; } }
/* ========================================================= MWC Lead Form Pattern v2026.06 Client-side validation + descriptive error handling + submit lifecycle (loading -> success | error). Hook real submission inside submitForm() below. ========================================================= */ (function () { 'use strict'; const form = document.getElementById('mwc-leadform'); if (!form) return; const banner = form.querySelector('#mwc-form-banner'); const submitBtn = form.querySelector('.mwc-submit'); // ---- Validators --------------------------------------------------------- const VALIDATORS = { name: (value) => { const v = value.trim(); if (!v) return 'Please enter your name so we can address you correctly.'; if (v.length < 2) return 'Name must be at least 2 characters.'; if (v.length > 80) return 'Name is too long. Please shorten to 80 characters.'; if (!/^[\p{L} '\-\.]+$/u.test(v)) return 'Please use only letters, spaces, hyphens, and apostrophes.'; return null; }, phone: (value) => { const digits = value.replace(/\D/g, ''); if (!digits) return 'Please enter your phone number. We will call you within one business hour.'; if (digits.length < 10) return 'Phone number is too short. Please include the area code.'; if (digits.length > 11) return 'Phone number is too long. US numbers are 10 digits.'; if (digits.length === 11 && digits[0] !== '1') return 'Phone number must be a valid US number.'; // Basic invalid-number guards if (/^(\d)\1{9,}$/.test(digits.replace(/^1/, ''))) return 'Please enter a real phone number.'; return null; }, email: (value) => { const v = value.trim(); if (!v) return 'Please enter a valid email address.'; if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v)) return 'That email does not look right. Please check and try again.'; if (v.length > 254) return 'Email is too long.'; return null; }, location: () => { const checked = form.querySelector('input[name="location"]:checked'); if (!checked) return 'Please select the center closest to you.'; return null; }, consent: () => { const c = form.querySelector('input[name="consent"]'); if (c && !c.checked) return 'Please agree to receive messages so we can confirm your appointment.'; return null; } }; // ---- Phone auto-format -------------------------------------------------- const phoneInput = form.querySelector('input[name="phone"]'); if (phoneInput) { phoneInput.addEventListener('input', (e) => { let d = e.target.value.replace(/\D/g, '').slice(0, 10); if (d.length > 6) e.target.value = `(${d.slice(0,3)}) ${d.slice(3,6)}-${d.slice(6)}`; else if (d.length > 3) e.target.value = `(${d.slice(0,3)}) ${d.slice(3)}`; else if (d.length > 0) e.target.value = `(${d}`; else e.target.value = ''; }); } // ---- Field state helpers ------------------------------------------------ function setError(field, message) { const wrap = field.closest('.mwc-field, .mwc-locations'); if (!wrap) return; wrap.classList.remove('is-valid'); wrap.classList.add('is-error'); const err = wrap.querySelector('.mwc-field__error'); if (err) err.textContent = message; } function setValid(field) { const wrap = field.closest('.mwc-field, .mwc-locations'); if (!wrap) return; wrap.classList.remove('is-error'); wrap.classList.add('is-valid'); const err = wrap.querySelector('.mwc-field__error'); if (err) err.textContent = ''; } function clearState(field) { const wrap = field.closest('.mwc-field, .mwc-locations'); if (!wrap) return; wrap.classList.remove('is-error', 'is-valid'); const err = wrap.querySelector('.mwc-field__error'); if (err) err.textContent = ''; } // ---- Field-level validation on blur + on input clear ------------------- form.querySelectorAll('input[name], select[name]').forEach((input) => { const fn = VALIDATORS[input.name]; if (!fn) return; input.addEventListener('blur', () => { const msg = fn(input.value || ''); if (msg) setError(input, msg); else setValid(input); }); input.addEventListener('input', () => { // Clear error as soon as the user types so they aren't yelled at const wrap = input.closest('.mwc-field, .mwc-locations'); if (wrap && wrap.classList.contains('is-error')) clearState(input); }); input.addEventListener('change', () => { if (input.type === 'radio' || input.type === 'checkbox') { const msg = fn(input.value || ''); if (msg) setError(input, msg); else setValid(input); } }); }); // ---- Full validation ---------------------------------------------------- function validateAll() { let firstErrorField = null; Object.keys(VALIDATORS).forEach((key) => { const field = form.querySelector(`[name="${key}"]`); if (!field) return; const msg = VALIDATORS[key](field.value || ''); if (msg) { setError(field, msg); if (!firstErrorField) firstErrorField = field; } else { setValid(field); } }); return firstErrorField; } // ---- Submission --------------------------------------------------------- async function submitForm(payload) { // REPLACE this stub with your real POST to GHL, Zapier, or your endpoint. // Example for GHL inbound webhook: // // const res = await fetch('https://services.leadconnectorhq.com/hooks/.../webhook-trigger/...', { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify(payload) // }); // if (!res.ok) throw new Error('Network response was not ok'); // return res.json(); // // For demo: simulate 800ms latency + success return new Promise((resolve) => setTimeout(() => resolve({ ok: true }), 800)); } form.addEventListener('submit', async (e) => { e.preventDefault(); form.classList.remove('has-error'); const firstError = validateAll(); if (firstError) { form.classList.add('has-error'); banner.textContent = 'Please correct the highlighted fields and try again.'; firstError.focus({ preventScroll: false }); firstError.scrollIntoView({ behavior: 'smooth', block: 'center' }); return; } // Collect data const data = Object.fromEntries(new FormData(form).entries()); data.source_url = window.location.href; data.submitted_at = new Date().toISOString(); // Submit form.classList.add('is-submitting'); submitBtn.setAttribute('aria-busy', 'true'); try { await submitForm(data); form.classList.remove('is-submitting'); form.classList.add('is-success'); // Optional: send GA / Meta / Google Ads conversion event here // window.dataLayer && window.dataLayer.push({ event: 'lead_submit' }); } catch (err) { form.classList.remove('is-submitting'); form.classList.add('has-error'); banner.textContent = 'We could not submit your request. Please check your connection and try again, or call us directly at (804) 346-4636.'; submitBtn.removeAttribute('aria-busy'); console.error('[mwc-leadform] submit failed:', err); } }); })();

Hard Rules

Never violate these.

×
Light or cream form surfaceForms are always Midnight Navy. White surfaces fade into the page and convert worse.
×
Hairline gray borders on white fieldsUse white fields with no border at rest and a 2px orange focus border.
×
Placeholder-only labelsEvery field has a visible uppercase label above it.
×
Flat CTA with no arrow or glowPrimary CTA always has the orange glow box-shadow and the right arrow.
×
"Submit" or "Get Started" button copyUse Book / Schedule / Claim / Reserve + Appointment.
×
Generic errors like "Invalid" or "Required"Tell the user exactly what is wrong and exactly how to fix it.
×
Words "consult," "consultation," "visit"Always "appointment." The headline, the button, the success state.
×
More than 5 fields on a lead formName, phone, location, consent, submit. Email optional and only if needed.