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.
Module contract
Section titled “Module contract”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.
Discovery
Section titled “Discovery”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>export default function (element: HTMLElement, dataset: DOMStringMap) { const variant = dataset.heroVariant ?? "default"; // ...}Dataset keys are camelCase: data-hero-variant → dataset.heroVariant.
File naming
Section titled “File naming”| Webflow attribute | Module 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.
DOM wiring conventions
Section titled “DOM wiring conventions”- Put
data-module="<name>"on the root element the module owns. - Use child hooks like
data-el,data-part, or descriptivedata-*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>Lifecycle imports
Section titled “Lifecycle imports”Import hooks from @/modules/_:
import { onDestroy, onPageIn, onPageOut, onView, onTrack } from "@/modules/_";| Hook | Purpose |
|---|---|
onDestroy | Cleanup when page transitions out |
onPageIn | Entrance animation after mount |
onPageOut | Exit animation before destroy |
onView | Intersection Observer wrapper |
onTrack | Scroll progress tracking |
See Component lifecycle for full hook behavior and page transition flow.
Shared services
Section titled “Shared services”Prefer framework services over ad-hoc listeners:
import { Raf, Resize } from "@lib/subs";import { Scroll } from "@lib/scroll";import gsap from "@lib/gsap";- Scroll system — Lenis smooth scroll
- Observer & track — viewport and scroll progress
- Subscriptions — Raf and Resize
CSS rules
Section titled “CSS rules”- 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>.cssand@importit fromsrc/styles/app.css. - Never add layout or typography rules to the repo unless Webflow cannot express them.
Webflow Designer mode
Section titled “Webflow Designer mode”The framework detects when the page runs inside the Webflow Designer and adjusts behavior (e.g. disables scroll smoothing). See Webflow integration.
Example module
Section titled “Example module”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); });}- Building a component — full tutorial
- Component lifecycle — hooks and page transitions
- AI edits — ask the agent to create modules with
/build