| name | esl |
| description | Use when writing, reviewing, or refactoring consumer code that uses @exadel/esl / Exadel Smart Library. Covers ESLBaseElement, ESLMixinElement, decorators, host model, public module imports, event listeners, traversing queries, media rules, and idiomatic ESL web component patterns. |
| package | @exadel/esl |
| packageVersion | 6.2.0 |
ESL Consumer Code
You are generating or reviewing consumer code that uses @exadel/esl.
Your goal is to produce idiomatic ESL code that uses the library primitives instead of raw DOM boilerplate.
Use this skill for:
- custom elements based on
ESLBaseElement
- mixins based on
ESLMixinElement
- code using
@exadel/esl/modules/...
- ESL decorators such as
@attr, @boolAttr, @jsonAttr, @listen
- ESL event, traversal, media, and utility APIs
If available, use reference docs in this skill package for deeper API details:
references/esl-core.md
references/esl-review.md
Public import rules
For consumer projects, prefer public module entrypoints:
import {ESLBaseElement} from '@exadel/esl/modules/esl-base-element/core';
import {ESLMixinElement} from '@exadel/esl/modules/esl-mixin-element/core';
import {ESLMediaQuery, ESLMediaRuleList} from '@exadel/esl/modules/esl-media-query/core';
import {attr, boolAttr, jsonAttr, prop, listen, ready} from '@exadel/esl/modules/esl-utils/decorators';
Rules:
- Prefer
@exadel/esl/modules/.../core public entries.
- Root import from
@exadel/esl is acceptable only when the consumer setup supports tree-shaking appropriately.
- Do not import from ESL internal implementation paths.
- Do not invent private paths when a public
core entry exists.
Choose the correct host model first
Before writing code, decide the host model:
- Use
ESLBaseElement for a new custom tag:
<my-element></my-element>
- Use
ESLMixinElement for behavior attached to an existing element by attribute:
<div my-mixin></div>
Hard rule:
- In
ESLBaseElement, the DOM host is this.
- In
ESLMixinElement, the DOM host is this.$host.
Do not treat a mixin instance as the DOM element. For mixins, read and mutate the real element through this.$host or host-aware ESL helpers.
Registration and lifecycle
Every ESL component or mixin must follow registration and lifecycle contracts:
export class MyElement extends ESLBaseElement {
static override is = 'my-element';
protected override connectedCallback(): void {
super.connectedCallback();
}
protected override disconnectedCallback(): void {
super.disconnectedCallback();
}
}
MyElement.register();
Rules:
- Set
static is before calling register().
- Call
register() once for the class.
- Always call
super.connectedCallback().
- Always call
super.disconnectedCallback().
- Do not mutate
static is after registration.
- Custom element and mixin names should contain a dash.
Prefer ESL decorators
Use decorators for attribute, property, and event wiring.
Prefer:
@attr for string, number, inherited, custom, default-enabled, or tri-state values
@boolAttr for boolean presence attributes only
@jsonAttr for object-like config attributes
@prop for prototype-level constants or provider-backed values
@listen for stable class-owned event listeners
@ready only when logic must wait for DOM readiness
Do not manually write repetitive getAttribute, setAttribute, hasAttribute, addEventListener, or removeEventListener logic when an ESL decorator or helper expresses the same behavior.
Use @attr, not @boolAttr, when the value is not a simple presence boolean.
Use host-aware ESL helpers
Inside ESLBaseElement and ESLMixinElement, prefer host-aware shortcuts:
this.$$find(selector)
this.$$findAll(selector)
this.$$cls(className, state?)
this.$$attr(name, value?)
this.$$fire(eventName, init?)
this.$$on(...)
this.$$off(...)
this.$$error(error, key?)
These helpers target the correct host:
this for ESLBaseElement
this.$host for ESLMixinElement
Use this.$$cls(...) and this.$$attr(...) for host state reflection instead of direct classList or attribute operations on the host.
Event handling
Default choice:
- use
@listen for stable listeners that belong to the class
@listen('click')
protected _onClick(e: MouseEvent): void {
}
Use $$on / $$off when:
- the listener is conditional
- the target changes at runtime
- the event type changes at runtime
- you need manual subscription control
@listen({event: 'resize', target: 'window', auto: false})
protected _onResize(): void {
}
protected override connectedCallback(): void {
super.connectedCallback();
this.$$on(this._onResize);
}
protected override disconnectedCallback(): void {
this.$$off(this._onResize);
super.disconnectedCallback();
}
Do not use raw addEventListener / removeEventListener for stable component-owned listeners when @listen can be used.
Traversing and DOM lookup
Use $$find / $$findAll when lookup is component-relative or user-configurable.
ESL traversing syntax can express relationships such as:
'' current host
::parent
::closest(.selector)
::child(button)
::find(.item)
::next
::prev
::visible
Examples:
this.$$find('');
this.$$find('::closest(esl-panel)');
this.$$find('::find(button, a)::not([hidden])');
this.$$findAll('::find(.item)::visible');
Native querySelector is allowed when a simple element-scoped CSS lookup is enough, but prefer ESL traversing when the selector represents component relationships.
Media and responsive behavior
Prefer ESL media utilities over raw matchMedia wiring:
- use
ESLMediaQuery when the code needs to react to a media condition
- use
ESLMediaRuleList when a config value changes by media condition
import {ESLMediaQuery} from '@exadel/esl/modules/esl-media-query/core';
@listen({event: 'change', target: ESLMediaQuery.for('@-sm')})
protected _onSmallViewportChange(): void {
}
Use ESLMediaRuleList for values like:
'default | @xs => compact | @+md => full'
Do not manually implement breakpoint parsing or media listener cleanup when ESL media utilities fit the problem.
Attribute change handling
When overriding attributeChangedCallback, guard expensive logic:
protected override attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
if (oldValue === newValue) return;
super.attributeChangedCallback(name, oldValue, newValue);
}
Remember:
ESLBaseElement observes attributes listed in static observedAttributes
ESLMixinElement observes static observedAttributes plus its own activation attribute
Common anti-patterns
Avoid:
- Importing from ESL internal implementation files.
- Choosing
ESLBaseElement when behavior should be a mixin.
- Choosing
ESLMixinElement but mutating this instead of this.$host.
- Forgetting
register().
- Forgetting
super.connectedCallback() or super.disconnectedCallback().
- Using raw event listeners for stable class-owned events.
- Using
@boolAttr for default-enabled or tri-state values.
- Assuming
@jsonAttr accepts only strict JSON.
- Reimplementing class, attribute, traversal, media, or event helpers already provided by ESL.
- Making public API depend on internal ESL repository paths.
Minimal good examples
Custom element:
import {ESLBaseElement} from '@exadel/esl/modules/esl-base-element/core';
import {attr, boolAttr, listen} from '@exadel/esl/modules/esl-utils/decorators';
export class AppCard extends ESLBaseElement {
static override is = 'app-card';
@attr({defaultValue: ''}) public heading: string;
@boolAttr() public selected: boolean;
protected override connectedCallback(): void {
super.connectedCallback();
this.$$cls('app-card-ready', true);
}
@listen('click')
protected _onClick(): void {
this.$$fire('app-card:click');
}
}
AppCard.register();
Mixin:
import {ESLMixinElement} from '@exadel/esl/modules/esl-mixin-element/core';
import {attr, listen} from '@exadel/esl/modules/esl-utils/decorators';
export class AppTrackClick extends ESLMixinElement {
static override is = 'app-track-click';
@attr({defaultValue: ''}) public eventName: string;
@listen('click')
protected _onClick(): void {
this.$$fire('app-track-click:trigger', {
detail: {eventName: this.eventName}
});
}
}
AppTrackClick.register();
Final checklist
Before returning ESL code, verify:
- Correct host model is used:
- custom tag →
ESLBaseElement
- attribute behavior →
ESLMixinElement
- Mixins use
this.$host or host-aware $$* helpers for DOM host operations.
- Imports use public
@exadel/esl/modules/... entries.
static is is defined before register().
register() is present.
- Lifecycle overrides call
super.
- Attributes/properties use ESL decorators where appropriate.
- Stable listeners use
@listen.
- Dynamic listeners use
$$on / $$off with cleanup.
- Component-relative lookup uses
$$find / $$findAll where appropriate.
- Responsive logic uses
ESLMediaQuery or ESLMediaRuleList where appropriate.
- No internal ESL implementation paths are used.
- No raw DOM boilerplate duplicates an ESL primitive.
- The code is small, readable, and easy for a consumer project to extend.