Skip to content
Download

Building a component

This tutorial walks through building a component (module) from scratch — structure, logic, styling, and wiring it to Webflow.

A counter module: a simple interactive element with increment/decrement buttons and a display. It demonstrates the module pattern, DOM wiring, lifecycle hooks, and styling.

  • A project created in Ssscript (Quick start if needed).
  • Basic familiarity with the editor.

In Code view (⌘2 / Ctrl+2), right-click the src/modules/ folder in the file tree and choose New File. Name it counter.ts.

Every module exports a default function that receives the DOM element and its dataset:

export default function (element: HTMLElement, dataset: DOMStringMap) {
// Module logic goes here
}

The module auto-mounts on any HTML element with data-module="counter".

Replace the file contents with:

import { onDestroy } from "@/modules/_";
export default function (element: HTMLElement, dataset: DOMStringMap) {
const display = element.querySelector(
"[data-counter-display]",
) as HTMLElement | null;
const increment = element.querySelector(
"[data-counter-increment]",
) as HTMLElement | null;
const decrement = element.querySelector(
"[data-counter-decrement]",
) as HTMLElement | null;
let count = parseInt(dataset.counterStart ?? "0", 10);
function update() {
if (display) display.textContent = String(count);
}
function handleIncrement() {
count++;
update();
}
function handleDecrement() {
count--;
update();
}
increment?.addEventListener("click", handleIncrement);
decrement?.addEventListener("click", handleDecrement);
update();
onDestroy(() => {
increment?.removeEventListener("click", handleIncrement);
decrement?.removeEventListener("click", handleDecrement);
});
}

The module:

  • Queries child elements using data-* attributes.
  • Reads an initial value from data-counter-start (optional).
  • Listens for clicks and updates the display.
  • Cleans up event listeners on destroy (important for page transitions).

Open src/styles/app.css and add styles. Only use app.css for CSS that Webflow can’t handle — in most cases, layout and typography should live in Webflow classes. For this example, add minimal animation CSS:

[data-module="counter"] [data-counter-display] {
transition: transform 0.15s ease-out;
}
[data-module="counter"] [data-counter-display].bumped {
transform: scale(1.1);
}

Then update the module to toggle the class briefly on change:

function update() {
if (display) {
display.textContent = String(count);
display.classList.add("bumped");
setTimeout(() => display.classList.remove("bumped"), 150);
}
}

In your Webflow site, create the DOM structure that the module expects. You can do this manually in the Designer or use the @@webflow agent mode if you have Webflow MCP connected.

The required structure:

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

Key attributes:

  • data-module="counter" — tells the module system to mount src/modules/counter.ts on this element.
  • data-counter-start — optional initial value.
  • data-counter-display, data-counter-increment, data-counter-decrement — child selectors the module uses.

Open the terminal (⌘J) and click Run to start the dev server. Open http://localhost:6454 in your browser (or check the embedded preview if your Webflow site loads the dev URL).

Click the buttons and verify the counter increments and decrements with the scale animation.

You can also ask the AI to build modules for you. Try:

@@build Create a scroll-triggered fade-in module that animates elements into view when they enter the viewport

The agent follows the same module pattern — creating the file in src/modules/, using lifecycle hooks, and providing a Webflow DOM handoff with the required data-* attributes.