Skip to content
Download

Module system

The module system connects Webflow markup to TypeScript. Each interactive feature is one file in src/modules/ that auto-mounts when the page loads.

Every module exports a default function:

export default function (element: HTMLElement, dataset: DOMStringMap) {
// Module logic runs here when the element is discovered
}

The module receives an existing DOM element — it does not create markup. Query children with element.querySelector(...) using data-* hooks defined in Webflow.

At startup, createCycles() scans the DOM for elements with data-module attributes, dynamically imports the matching file from src/modules/, and runs the default export:

<div data-module="hero" data-hero-variant="large">...</div>
src/modules/hero.ts
export default function (element: HTMLElement, dataset: DOMStringMap) {
const variant = dataset.heroVariant ?? "default";
// ...
}

Dataset keys are camelCase: data-hero-variantdataset.heroVariant.

Webflow attributeModule file
data-module="hero"src/modules/hero.ts
data-module="sss-counter"src/modules/sss-counter.ts

Use kebab-case for multi-word module names. One module per file.

  • Put data-module="<name>" on the root element the module owns.
  • Use child hooks like data-el, data-part, or descriptive data-* attributes — not element IDs.
  • Do not use document.getElementById(...) for module wiring.
  • Do not build DOM from JavaScript (createElement, innerHTML). Describe required structure in a handoff or create it in Webflow Designer (or via Webflow MCP when connected).

Example structure:

<div data-module="sss-counter" data-counter-start="0">
<button data-counter-decrement>-</button>
<span data-counter-display>0</span>
<button data-counter-increment>+</button>
</div>

Import hooks from @/modules/_:

import { onDestroy, onPageIn, onPageOut, onView, onTrack } from "@/modules/_";
HookPurpose
onDestroyCleanup when page transitions out
onPageInEntrance animation after mount
onPageOutExit animation before destroy
onViewIntersection Observer wrapper
onTrackScroll progress tracking

See Component lifecycle for full hook behavior and page transition flow.

Prefer framework services over ad-hoc listeners:

import { Raf, Resize } from "@lib/subs";
import { Scroll } from "@lib/scroll";
import gsap from "@lib/gsap";
  • Webflow owns layout, typography, color, spacing, and presentation.
  • Repo CSS is only for Webflow-impossible cases: @keyframes, pre-JS hidden states, complex animations.
  • Put feature CSS in src/styles/mod/<name>.css and @import it from src/styles/app.css.
  • Never add layout or typography rules to the repo unless Webflow cannot express them.

The framework detects when the page runs inside the Webflow Designer and adjusts behavior (e.g. disables scroll smoothing). See Webflow integration.

import { onDestroy, onPageIn } from "@/modules/_";
import gsap from "@lib/gsap";
export default function (element: HTMLElement, dataset: DOMStringMap) {
const button = element.querySelector("[data-el='cta']");
function handleClick() {
element.classList.toggle("active");
}
button?.addEventListener("click", handleClick);
onPageIn(async () => {
await gsap.from(element, { opacity: 0, y: 20, duration: 0.5 });
});
onDestroy(() => {
button?.removeEventListener("click", handleClick);
});
}