Contents:
- Description
- Example
- Install
- Component API
- Other utils
- Cross-module communication with CustomEvents
- Claude Code skill
- Run tests
- Code quality
A lightweight vanilla JavaScript component library for simple DOM element management without external dependencies.
Features:
- Each
Componentinternally holds anHTMLElement. - Inside the module, you work only with
Component. - If you need to extend a component’s capabilities for working with an
HTMLElement, create a new component that inherits fromComponentand interact with the DOM node inside that class. - The
ComponentextendsEventEmitter, so you can use the events API.
Simple counter, similar to the Vue example.
<!-- HTML -->
<button id="api-button-count">
Count is: <span id="api-count">0</span>
</button>
<script type="module">
import { default as Component } from '/js/components/component.js';
const button = Component.fromId('api-button-count');
const counter = Component.fromId('api-count');
const count = 0;
button.onEvent('click', () => {
counter.text(++count);
});
</script>Full example here. See below how to run tests.
Installation is not required. Copy the contents of src to your project's public folder.
- Component controls an HTMLElement.
- Component Collection - a set of components.
- Form - extended component with form submission capability.
- EventEmitter - provides the component with the ability to subscribe to and trigger events.
Contents:
- Create a component
- Default usage
You can create a component in the following ways:
-
constructmethod:const div = document.createElement('div'); const d = new Component(div);
-
from HTMLElement class
Component.fromClass(className) -
from HTMLElement name
Component.fromName(name) -
from HTMLElement id
Component.fromId(id) -
from parameters:
const component = Component.fromParams( 'div', { class: 'some-class' // class is an attribute name }, 'some inner text' );
After creating a component, you can use the built-in functions:
| API name | description |
|---|---|
appendByClass(className) |
Finds an element by class name and appends itself as a child. |
appendById(id) |
Finds an element by id and appends itself as a child. |
enable() |
Makes the component accessible. |
dataset(name) |
Gets a data attribute. |
disable() |
Makes the component inaccessible. |
onEvent(name, callback) |
Subscribes to a DOM event. |
hide() |
Hides the component. |
show(type = 'block') |
Displays the component. |
text(newText = '') |
Sets inner text. |
onHide(callback) |
Registers a callback to handle hiding. |
onShow(callback) |
Registers a callback to handle showing. |
getValue() |
Accesses the value attribute. |
setValue(val) |
Updates the value attribute. |
getName() |
Accesses the name attribute. |
setName(val) |
Updates the name attribute. |
Contents:
- Create a component collection
- Default usage
You can create a component collection in the following ways:
-
constructmethod:const div1 = document.createElement('div'); const div2 = document.createElement('div'); const elements = [div1, div2]; const dc = new ComponentCollection(elements);
-
from HTMLElement class
ComponentCollection.fromClass(className) -
from HTMLElement tag
ComponentCollection.fromTag(tagName)
After creating a component collection, you can use the built-in functions:
| API name | description |
|---|---|
disable() |
Makes the components inaccessible. |
enable() |
Makes the components accessible. |
hide() |
Hides the components. |
onEvent(name, callback) |
Subscribes to a DOM event. |
onHide(callback) |
Registers a callback to handle hiding. |
onShow(callback) |
Registers a callback to handle showing. |
show(type = 'block') |
Displays the components. |
text(newText = '') |
Adds inner text. |
A Form is an extended component that provides the following additional capabilities:
- a
submit()method to control the submission flow.
EventEmitter is used to subscribe to and emit events. The Component extends EventEmitter, so each component can subscribe to another component’s events. You can also execute other module logic when events occur.
Example:
// Creation of an extended component
class SuperButton extends Component {
someFunction() {
// internal logic
this.emit('someEvent', ...args);
// internal logic
}
}
const button = Component.fromId('id1');
const superButton = SuperButton.fromId('id2');
superButton.on('someEvent', (...args) => {
// 1. work with event's arguments
// 2. work with another component
button.disable();
// 3. do another work
console.log('message');
});API:
| API | description |
|---|---|
clear(name) |
Deletes the event by name, or deletes all events if no name is provided. |
count(name) |
Returns the number of registered callbacks. |
emit(name, ...args) |
Emits an event with the provided arguments. |
listeners(name) |
Returns the event's callbacks. |
names() |
Returns the names of registered events. |
on(name, fn) |
Subscribes a unique callback to the event. |
once(name, fn) |
Subscribes a callback that is removed after the first execution. |
remove(name, fn) |
Removes a callback from an event. |
Contents:
- URL builder — helps build URLs from path parts, query parameters, and optional fragments.
Urlbuilder is an utilite to build URLs from path parts, query parameters and fragment.
- Create urbuilder instance
- Form an URL
- Full Example
- Additional
First. The urbuilder need a target to form the path. You can choose from 2 constiants:
target- build a standart path.dynamicTarget(dynamicRoot)- used to swap a special root segment. Based ontarget.
Second. You can pass optional parameters scheme and authority to form an absolute url.
Finaly. To create an urlbuilder instance run the function constructor urlbuilder(target, 'https', 'example.com').
import urlbuilder from '/js/urlbuilder/createUrlBuilder.js';
import target from '/js/urlbuilder/target.js';
// Absolute URL
const ub = urlbuilder(target, 'https', 'example.com');
// Relative URL
const ub2 = urlbuilder(target);Once you created an urlbuilder instance, you can start to form urls:
- Create simple URL
- Create URL with queries
- Create URL with fragment
const parts = ['articles'];
const url = ub(parts); // 'https://example.com/articles'If you need query params, follow next steps:
import Query from '/js/urlbuilder/query.js';
const parts = ['articles'];
const queries = [new Query('order', 'desc'), new Query('page', '2')];
const url = ub(parts, queries); // 'https://example.com/articles?order=desc&page=2'const parts = ['articles'];
const url = ub(parts, [], 'table-1'); // 'https://example.com/articles#table-1'import urlbuilder from '/js/urlbuilder/createUrlBuilder.js';
import target from '/js/urlbuilder/target.js';
import Query from '/js/urlbuilder/query.js';
const ub = urlbuilder(target, 'https', 'example.com');
const query1 = new Query('order', 'desc');
const query2 = new Query('page', '2');
const url = ub(['articles'], [query1, query2], 'header1'); // 'https://example.com/articles?order=desc&page=2#header1'-
Use
dynamicTargetto swap a special root segment:import dynamicTarget from '/js/urlbuilder/dynamicTarget.js'; const dynamic = dynamicTarget('api'); const ub = urlbuilder(dynamic); ub(['root', 'users']); // '/api/users'
-
You don't need to encode query parameters, urlbuilder does it itself.
When you split your page into independent ES modules, you can use the browser's built-in CustomEvent + document as a lightweight message bus. Each module works in isolation — the only shared contract is the event name and the detail payload.
The recommended approach:
- Submodule — extends
Componentand encapsulates dispatch logic inside a method. - Main module — listens on
documentand reacts to events without knowing anything about the submodule's internals.
<!-- Main module -->
<script type="module">
import { default as Component } from '/js/components/component.js';
const counter = Component.fromId('api-count');
document.addEventListener('submoduleEvent', (e) => {
counter.text(e.detail);
});
</script>
<!-- Submodule -->
<script type="module">
import { default as Component } from '/js/components/component.js';
class Submodule extends Component {
dispatch(count) {
document.dispatchEvent(new CustomEvent('submoduleEvent', {
detail: count
}));
}
}
const button = Submodule.fromId('api-button-count');
let count = 0;
button.onEvent('click', () => {
button.dispatch(++count);
});
</script>The submodule fires submoduleEvent with the updated count as detail; the main module catches it and updates the display. Neither module imports the other.
A Claude Code skill is included in skills/js-components/SKILL.md. It gives Claude instant access to the full API reference and common patterns so you don't need to describe the library in every prompt.
Install:
cp -r skills/js-components ~/.claude/skills/After copying, Claude will automatically suggest the skill whenever you work with Component, EventEmitter, ComponentCollection, Form, or urlbuilder.
All modules are tested. You can find tests in test dir. To run the tests, follow these steps:
- Run in the console
docker compose up --build. - Visit localhost:8080.
- Select the desired test suite.
- Open browser console and check tests' status.
- 68 tests in the test directory.
- The code is checked by ESLint. See .eslintrc.json for more details.