Philosophy
Progressive enhancement, not application framework
@studiometa/js-toolkit is designed for server-rendered websites — WordPress, Laravel, Symfony, static HTML, or any backend that produces HTML. Instead of owning the DOM (like React or Vue), it enhances existing markup with JavaScript behavior.
The core idea: your HTML is the source of truth. Components bind to it via data-* attributes — no virtual DOM, no JSX, no template compilation.
The data-attributes approach
Every concept in js-toolkit maps to a data-* attribute:
| Concept | HTML attribute | Purpose |
|---|---|---|
| Component binding | data-component="Name" | Identifies which class manages this element |
| Refs | data-ref="name" | Marks elements the component needs to access |
| Options | data-option-key="value" | Passes configuration from HTML to JavaScript |
This approach has several advantages:
- No build step for HTML — your server-rendered templates work as-is
- Inspectable — open DevTools and see exactly which components are active on which elements
- Portable — works with any backend (PHP, Python, Ruby, static HTML)
- Resilient — if JavaScript fails to load, the HTML still renders
How it compares
vs. React / Vue / Svelte
These frameworks own the rendering pipeline. They generate the DOM from JavaScript (or compiled templates). js-toolkit takes the opposite approach: the DOM already exists, and JavaScript adds behavior to it.
Use React/Vue/Svelte when you need a full client-side application with complex state, routing, and client-side rendering.
Use js-toolkit when you have server-rendered HTML and need to add interactive behaviors — accordions, carousels, scroll animations, form validation, modals.
vs. Alpine.js / Stimulus
Alpine.js and Stimulus share js-toolkit's philosophy of enhancing server-rendered HTML. The key differences:
- Class-based architecture — js-toolkit uses ES classes with inheritance. Components are proper classes, making them easy to extend, type with TypeScript, and test.
- Built-in services — scroll, resize, pointer, keyboard, RAF, and more — automatically tied to component lifecycle. No need to manually add/remove event listeners.
- Scoped resolution — refs and child components are scoped to the nearest
data-componentboundary, preventing accidental cross-component access. - Tree-shakeable utilities — a rich set of DOM, math, CSS, string, and animation helpers you can import individually.
Design principles
- Convention over configuration — naming conventions drive behavior. Name a method
onBtnClickand it automatically listens for click events on thebtnref. - Automatic lifecycle — services are enabled when a component mounts and disabled when it destroys. No manual cleanup.
- Lazy by default — async component imports are only fetched if a matching DOM element exists. Automatic code-splitting with zero configuration.
- TypeScript-first — full type definitions for refs, options, children, and service props. Works with JSDoc too.
Further reading
- Getting Started — install and create your first component
- API Reference — the complete
Baseclass API - Component Tree — how components are resolved and mounted
- Service Architecture — the singleton service pattern