Lazy Loaded Form
HubSpot forms without the layout shift
Embed any HubSpot form while deferring its embed script until visitors actually need it. Lazy Loaded Form reserves space up front to eliminate layout shift (CLS) and keeps heavy form JavaScript off the critical path for a faster LCP — so your Core Web Vitals stay green without sacrificing conversions. Works with both new (2024+) and legacy forms, with load-on-scroll, load-on-click, or on-idle strategies.
Form Heading
Better Core Web Vitals, without losing the form
Standard HubSpot form embeds tend to load a heavy script on page load and pop the form in after paint — dragging down your LCP and triggering the layout shift that tanks your CLS score. Lazy Loaded Form flips that: it renders reserved space instantly and waits to fetch the form until a visitor actually needs it. You get a faster, more stable page that keeps Google happy — and a form that's ready the moment it matters.
Eliminate layout shift (CLS)
Reserved space is rendered up front, so surrounding content never jumps when the form appears.
Faster LCP
The ~200KB+ of HubSpot form JavaScript is deferred off the critical path, letting your main content paint first.
Load exactly when needed
Choose load-on-scroll, load-on-click, or on-idle so the form arrives at the right moment for each placement.
Smart preloading
On-scroll loading fetches the form just before it enters view, so any size change happens off-screen and never counts against CLS.
New and legacy forms
Works with both HubSpot's 2024+ form editor and classic forms, auto-detecting the right embed method.
Polished loading state
A built-in shimmer placeholder (with reduced-motion support) keeps the experience smooth while the form loads.
No layout guesswork
Style, size, and alignment controls plus per-instance form styling let it match any page without touching code.
A Little Bit About How it Works
Need This Module Customized for Your Brand or Function?
(or any other module for that matter?)
Let A Design Link tailor this event calendar to your exact specifications—from custom color schemes and unique layouts to special features and integrations. Our HubSpot experts can modify any aspect of this module or build completely custom functionality to match your vision.
Purchase a development package and get:
- Expert HubSpot CMS customization
- Brand-perfect styling and design tweaks
- Custom feature development
- Performance optimization
- Ongoing support and maintenance
Perfect for module customization, theme development, and HubSpot CMS consulting.
Documentation
Lazy Loaded Form
A HubSpot CMS module that embeds a HubSpot form while deferring the forms embed script until the form is actually needed. It reserves the form's vertical space up front so the page never jumps when the form appears.
The result: better CLS (no layout shift), better LCP (the ~200KB+ of HubSpot form JavaScript is kept off the critical path), and less main-thread work on initial load.
- Categories: Forms & Buttons, Functionality
- Content types: Landing pages, site pages, blog posts, blog listing
- Supports: New forms (2024+ editor) and Legacy / classic forms
Quick Links
- Why this module exists
- How it works
- Loading strategies
- Reserving space (the CLS control)
- Choosing and configuring the form
- Fields reference
- Form Style Overrides (legacy only)
- Accessibility
- Performance notes / best practices
- Troubleshooting
- Known limitations
Why this module exists
HubSpot's standard form embed injects js.hsforms.net and renders the form client-side immediately. Two problems follow:
- LCP — the browser downloads and executes a large form bundle as part of the initial load, competing with content paint.
- CLS — the form renders after first paint and pushes surrounding content down, producing a layout shift.
This module fixes both:
- Layout is server-rendered with a reserved-height placeholder, so the browser paints instantly and the space for the form already exists.
- The embed script is loaded on demand — on scroll, on interaction, or when the browser is idle. When "on scroll" preloading is used, the form is fetched before it enters the viewport, so any height change happens off-screen and is not counted toward CLS at all (CLS only measures shifts inside the viewport).
How it works (technical overview)
module.html -> Renders a reserved-space placeholder + data-* config.
Resolves the form ID / portal ID / region server-side.
module.css -> Namespaced (blk_lzf) base styles + skeleton shimmer.
module.js -> Reads data-* attributes, loads the correct embed script
on the chosen trigger, mounts the form, removes the
placeholder.
The template writes the form's configuration onto a container element as data attributes:
| Attribute | Purpose |
|---|---|
data-form-id |
The HubSpot form ID (GUID or numeric) |
data-portal-id |
HubSpot account/portal ID |
data-region |
Data region (na1, na2, eu1) |
data-embed-version |
new or legacy — selects the embed method |
data-strategy |
viewport | interaction | idle | immediate |
data-root-margin |
Preload distance in px for the viewport strategy |
module.js then:
- Legacy forms → loads
js.hsforms.net/forms/embed/v2.jsand callshbspt.forms.create({ portalId, formId, region, target }). TheonFormReadycallback removes the placeholder. - New forms → loads
js.hsforms.net/forms/embed/<portalId>.js(once per portal) and inserts a.hs-form-frameelement with the data attributes. AMutationObserver(plus a timeout fallback) detects render and removes the placeholder.
Each embed script is fetched at most once per page and shared across every instance of the module.
Loading strategies
Set under Loading Behavior → When to load the form.
| Strategy | When the form loads | Best for |
|---|---|---|
| On scroll | Just before it scrolls into view (IntersectionObserver) | Below-the-fold forms (default) |
| On interaction | When the visitor clicks a "Load form" button | Leanest initial load; low-intent placements |
| When idle | After load, via requestIdleCallback |
Forms that should appear without a click but aren't urgent |
| Immediately | As soon as the module script runs | Above-the-fold / primary-CTA forms |
In the HubSpot page editor the module always loads immediately so authors can see and configure the form regardless of the selected strategy.
Preload distance (On scroll only)
root_margin (default 200px) controls how far below the viewport loading begins. Larger = the form is ready earlier (before it's seen), which keeps its height change off-screen and out of your CLS score. Smaller = leaner but the form may still be assembling as it enters view.
Reserving space (the CLS control)
Under Loading Behavior:
- Reserve space (default on) — reserves vertical space before the form loads.
- Reserved height (default 400px) — set this close to your form's real rendered height.
Set the reserved height accurately. If it matches the form, there's no gap and no jump. If it's much smaller than the form and the form loads while on-screen, the container will grow and cause a shift. With "On scroll" preloading, growth typically happens off-screen and doesn't count — but a close value is still the safest configuration, especially for above-the-fold forms.
Choosing and configuring the form
The Form type field determines how you pick the form.
New form (2024+ editor) — default
New forms are not available in HubSpot's module form picker (a HubSpot platform limitation, not a module bug). Instead, paste the form's embed code:
- In HubSpot go to Marketing → Forms and open your form.
- Click Share → Embed.
- Copy the full embed snippet and paste it into the module's Form embed code field. It looks like:
<script src="https://js.hsforms.net/forms/embed/1234567.js"></script>
<div class="hs-form-frame" data-region="na1"
data-form-id="0f1a2b3c-4d5e-..." data-portal-id="1234567"></div>
The module automatically extracts the form ID, portal ID, and region from the snippet. Pasting just the bare form ID also works — in that case the module falls back to the current account's portal ID and the region set under Advanced.
Legacy / classic form
Select Legacy / classic form to use HubSpot's native form picker dropdown. Portal ID comes from the account automatically; set the region under Advanced if your account is not na1.
Fields reference
Top level
| Field | Type | Notes |
|---|---|---|
| Form type | choice | New form (2024+ editor) (default) or Legacy / classic form |
| Form embed code | text | Shown for New forms. Paste the Share → Embed snippet. |
| Form | form | Shown for Legacy forms. HubSpot's native form picker. |
Content
| Field | Type | Default | Notes |
|---|---|---|---|
| Heading | text | (empty) | Optional heading above the form. |
| Heading Tag | choice | h2 |
For correct document outline / SEO. |
| Accessible Label | text | Form |
ARIA label for the form region. |
| Load Button Text | text | Load form |
Used only by the "On interaction" strategy. |
Loading Behavior
| Field | Type | Default | Notes |
|---|---|---|---|
| When to load | choice | viewport |
See Loading strategies. |
| Preload distance | number | 200 px |
Viewport strategy only. |
| Reserve space | boolean | true |
Toggles CLS-preventing space reservation. |
| Reserved height | number | 400 px |
Approximate rendered form height. |
Advanced
| Field | Type | Default | Notes |
|---|---|---|---|
| Form region | choice | na1 |
Account data region: na1, na2, or eu1. Used for legacy forms and as a fallback for new forms. Overridden by data-region in a pasted embed code. |
Styles (STYLE tab)
- Layout — max width, alignment, container padding.
- Colors — container background, heading color.
- Loading Placeholder — shimmer on/off, base/shine colors, corner radius.
- Load Button (interaction mode) — background, text color, corner radius.
- Form Style Overrides — Legacy forms only (see below).
Form Style Overrides (Legacy forms only)
Because legacy forms render as inline HTML (not an iframe), their fields can be restyled with CSS. This group is hidden unless Form type = Legacy, and is gated behind a master "Override HubSpot form styles" toggle. When off, the form inherits your HubSpot/theme styling untouched.
Available controls:
- Field spacing between fields.
- Field Labels — color, font size, weight.
- Inputs — text/background/border colors, focus border, border width, radius, padding, font size (defaults to 16px to prevent iOS zoom-on-focus).
- Checkboxes & Radio Buttons — accent color (via CSS
accent-color), option label color, control size. - Submit Button — background, hover, text color, font size, radius, padding, full-width toggle.
- Validation — error message / error border color.
All override CSS is scoped to the module instance (#blk_lzf_frame_<name>) so it never affects other forms on the page. Each color is guarded — leaving a picker empty inherits the existing style instead of blanking it.
New forms cannot be restyled this way; style them in HubSpot's form editor.
Accessibility
- The form container is a labelled
regionwitharia-busyreflecting load state. - A visually hidden
aria-livestatus announces "Loading form…", "Form loaded.", or an error. - The interaction button is a real
<button>with a visible focus ring. - The skeleton shimmer respects
prefers-reduced-motion. - A
<noscript>message is shown if JavaScript is disabled.
Performance notes / best practices
- Above-the-fold forms: use Immediately (or On scroll with a large preload distance) and set an accurate Reserved height.
- Below-the-fold forms: On scroll (default) is ideal — the form is ready before it's seen and the layout change never counts against CLS.
- Secondary / low-priority forms: On interaction avoids loading the form bundle entirely unless the visitor engages.
- Multiple instances on one page are safe — unique IDs prevent collisions and the embed script is loaded only once per page (per portal for new forms).
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| New form doesn't render | Confirm Form type = New form and that the pasted embed code contains data-form-id. |
| Form loads but is unstyled by overrides | Style Overrides only apply to Legacy forms. Style new forms in HubSpot. |
| Form appears blank on an EU account | Ensure the region is correct — in the pasted embed code (data-region="eu1") or the Advanced → Form region field for legacy forms. |
| Content still shifts when the form loads | Increase Reserved height to match the real form, and/or increase Preload distance. |
hbspt is not defined in console |
Don't mix embed types. The Form type toggle handles this — make sure it matches your form. |
Known limitations
- No native new-form picker. HubSpot does not expose new (2024+) forms to any custom-module field, so new forms are configured by pasting their embed code. This is a documented platform limitation with an open feature request.
- Field-level Style Overrides are legacy-only for the reason above.
- New-form "ready" detection is best-effort. The new embed offers no reliable inline ready callback, so a MutationObserver + timeout fallback is used. The reserved height makes the exact removal moment non-critical for CLS.