CustomElement
CustomElement is the base layer for Monster web components. Use it when you need your own tag, your own template and Shadow DOM encapsulation, but not yet the full form-associated behavior of CustomControl.
Build a minimal reusable element and understand the lifecycle you are opting into
This tutorial focuses on the smallest useful custom element: a custom tag with defaults, template output, registration and a clear separation between one-time init and DOM assembly.
Before You Start
Be comfortable with loading Monster at all
This page assumes the install and file layout are already clear. If not, start with First Page before you build a custom tag.
Know when Updater is enough
If you only need to bind an existing DOM subtree to data, use Templating. Reach for CustomElement when you truly need your own reusable tag and lifecycle.
Choose the Right Base Class
Use CustomElement when
You need a standalone tag, Shadow DOM, template composition and public options, but the element is not primarily a form control.
Use CustomControl when
You need value handling, form association, validation and ElementInternals-backed behavior. That is the right layer for select-like and input-like controls.
What the Base Class Gives You
Shadow DOM management
The base class owns the Shadow DOM setup. That is why lifecycle overrides should call super unless you know exactly which internal behavior you are replacing.
Template and option infrastructure
Defaults, template mapping and formatter markers already exist. Extend them instead of inventing parallel configuration layers.
Build a Minimal Element
1. Import the base class and registration helper
import {
CustomElement,
registerCustomElement,
} from "@schukai/monster/source/dom/customelement.mjs";2. Create the class and define the tag
class MyButton extends CustomElement {
static getTag() {
return "monster-my-button";
}
}3. Extend defaults instead of replacing them
Keep inherited behavior intact. Add only the options and template pieces you actually need.
class MyButton extends CustomElement {
static getTag() {
return "monster-my-button";
}
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: "<button>${label}</button>",
},
templateMapping: {
label: "Hello!",
},
});
}
}4. Register the element once
registerCustomElement(MyButton);5. Use the tag in HTML
<monster-my-button></monster-my-button>Result
The point of this example is not the button styling. It is the contract: explicit tag, explicit registration and explicit template ownership.
Template Mapping and Formatter Markers
Use templateMapping when a template should stay static but receive values from options. Only customize formatter markers if you truly need a different syntax boundary.
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: "<button><%label%></button>",
},
templateMapping: {
label: "Mapped label",
},
templateFormatter: {
marker: {
open: "<%",
close: "%>",
},
},
});
}Do not over-customize the template layer early
If every local control uses its own formatter syntax or mapping scheme, the library stops feeling like a system. Keep the defaults unless you have a strong reason.
Lifecycle Hooks You Actually Need
connectedCallback()
Use it for runtime work that depends on the element being in the DOM. Always call super.connectedCallback() so the base class can finish setup.
disconnectedCallback()
Use it to clean up listeners, timers or observers that you added yourself. Again, keep the parent call intact.
class MyButton extends CustomElement {
connectedCallback() {
super.connectedCallback();
// add listeners or runtime setup here
}
disconnectedCallback() {
super.disconnectedCallback();
// clean up your own resources here
}
}Init vs Assemble
Monster splits one-time object initialization from DOM assembly through initMethodSymbol and assembleMethodSymbol. Use them only when the normal lifecycle methods are not precise enough.
import {
CustomElement,
initMethodSymbol,
assembleMethodSymbol,
} from "@schukai/monster/source/dom/customelement.mjs";
class MyButton extends CustomElement {
[initMethodSymbol]() {
super[initMethodSymbol]();
// one-time initialization
}
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
// DOM-dependent assembly
}
}Common Mistakes
Skipping registration
Without registerCustomElement(), the tag never becomes a real custom element and the browser treats it as unknown markup.
Overriding lifecycle methods without super
That usually breaks internal setup, especially Shadow DOM and option initialization.
Choosing CustomElement when the control is really form-associated
If the element has a value, validation and form semantics, start from CustomControl instead.