Derive JSX prop types from your components — attributes, events, slots, children — instead of maintaining a parallel declare global block. One helper for most cases, specialized helpers when you need more.
All type helpers are re-exported from elements-kit/jsx-runtime. MaybeReactive also lives in elements-kit/signals.
ElementProps<C>
Raw JSX surface for a custom element. Composes attributes, flat properties, prop:*, events, slots, and children — withoutMaybeReactive wrapping. Use this when you need the underlying property/handler types directly (testing, manual property assignment, deriving sub-types).
A decorator that makes a class field reactive by automatically wrapping its value in a signal.
The field behaves like a normal property (get/set) but reactivity is tracked under the hood.
Any reads will subscribe to the signal and any writes will trigger updates.
@example
classCounter {
\@reactive() count:number=0;
}
constcounter=newCounter();
counter.count++; // Triggers reactivity
console.log(counter.count); // Subscribes to changes
@remarks ―
Equivalent to manually creating a private signal and getter/setter:
classCounter {
#count=signal(0);
getcount() { returnthis.#count(); }
setcount(value) { this.#count(value); }
}
reactive } from"elements-kit/signals";
import {
constSLOTS:typeofSLOTS
Symbol key for attaching a Slots instance to a custom element instance.
This prevent collisions with Element properties and are not meant to be treated as JSX children.
SLOTS,
classSlots<Kextendsstring>
A keyed collection of slot instances.
Slots are pre-created from the provided keys and lazily created on first access for unknown keys.
Full JSX prop type for a custom-element class (extends HTMLElement).
Composes every surface the element can receive from JSX:
Attributes — keys from static [ATTRIBUTES] (typed MaybeReactive<string | null>).
Keys also present on the instance are dropped here so the flat key carries the property type.
Flat properties — public instance fields, wrapped in MaybeReactive.
prop:* — explicit property assignment for every field.
Events — keys from declare static events: { ... } produce both
on:${K} and on${Capitalize<K>} typed handlers.
Slots — keys from [SLOTS] = Slots.new([...] as const) produce slot:${K}.
Children — children?: Child unless static children: never.
DOM attrs — the standard dom-expressions surface (class, style, ref, …).
@template ― C — the custom-element class (constructor type).
A class decorator that automatically wires up observedAttributes and attributeChangedCallback
from a static [ATTRIBUTES] map.
The this type inside attribute handlers is automatically inferred from the decorated class.
@example
\@attributes
classMyElementextendsHTMLElement {
static [ATTRIBUTES] = {
count(this:MyElement, value:string|null) {
this.count =Number(value);
},
};
}
attributes
class
classXRange
XRangeextends
var HTMLElement: {
new ():HTMLElement;
prototype:HTMLElement;
}
The HTMLElement interface represents any HTML element. Some elements directly implement this interface, while others implement it via an interface that inherits it.
Symbol key for attaching a Slots instance to a custom element instance.
This prevent collisions with Element properties and are not meant to be treated as JSX children.
SLOTS] =
classSlots<Kextendsstring>
A keyed collection of slot instances.
Slots are pre-created from the provided keys and lazily created on first access for unknown keys.
Create a typed Slots collection from a list of key names.
Pass the keys with as const so TS narrows them to a literal union —
this is what lets ElementProps<typeof Cls> synthesize slot:${K}
entries. Without as const, the array type widens to string[] and
the slot keys are lost.
@example
classCardextendsHTMLElement {
// ✅ literal keys flow through — "header" | "footer"
A decorator that makes a class field reactive by automatically wrapping its value in a signal.
The field behaves like a normal property (get/set) but reactivity is tracked under the hood.
Any reads will subscribe to the signal and any writes will trigger updates.
@example
classCounter {
\@reactive() count:number=0;
}
constcounter=newCounter();
counter.count++; // Triggers reactivity
console.log(counter.count); // Subscribes to changes
@remarks ―
Equivalent to manually creating a private signal and getter/setter:
Full JSX prop type for a custom-element class (extends HTMLElement).
Composes every surface the element can receive from JSX:
Attributes — keys from static [ATTRIBUTES] (typed MaybeReactive<string | null>).
Keys also present on the instance are dropped here so the flat key carries the property type.
Flat properties — public instance fields, wrapped in MaybeReactive.
prop:* — explicit property assignment for every field.
Events — keys from declare static events: { ... } produce both
on:${K} and on${Capitalize<K>} typed handlers.
Slots — keys from [SLOTS] = Slots.new([...] as const) produce slot:${K}.
Children — children?: Child unless static children: never.
DOM attrs — the standard dom-expressions surface (class, style, ref, …).
@template ― C — the custom-element class (constructor type).
Attribute keys that also appear on the instance are removed from the attribute slot, so the flat key carries the property type (e.g. number) rather than the handler’s string type.
At the JSX call site, the runtime wraps ElementProps<C> in MaybeReactiveProps<...> so parents may pass static values or signals/computed:
// JSX layer wraps via `MaybeReactiveProps<ElementProps<C>>`
<x-rangemin={0} /> // static
<x-rangemin={() =>0} /> // getter
<x-rangemin={signal(0)} /> // signal (a getter)
Plus the namespaced extras (class:, style:, prop:, slot:, ref) are layered on every intrinsic element via JsxNamespaces in src/jsx-runtime/index.ts.
Props<C>
Unified helper. Works on class instances, class constructors, or function components. Does not synthesize attribute / event / slot surfaces — use ElementProps for those.
so callers may pass either a
plain value or a reactive getter. Function-typed props (event handlers,
render callbacks) are wrapped too — the JSX runtime detects branded
signals/computed and re-binds on change. Optionality is preserved at the
key level — the | undefined stays at the prop, not inside the reactive.
so callers may pass either a
plain value or a reactive getter. Function-typed props (event handlers,
render callbacks) are wrapped too — the JSX runtime detects branded
signals/computed and re-binds on change. Optionality is preserved at the
key level — the | undefined stays at the prop, not inside the reactive.
// onClick: MaybeReactive<(e: Event) => void>; // computed handlers OK
// }
@see ― MaybeReactive
@see ― Props
MaybeReactiveProps<
typeRaw= {
count:number;
label?:string;
onClick: (e:Event) =>void;
}
Raw>;
// {
// count: MaybeReactive<number>;
// label?: MaybeReactive<string>;
// onClick: MaybeReactive<(e: Event) => void>;
// }
ReactiveProps<P>
Component-facing — every prop becomes a callable Computed<T> getter. The JSX runtime auto-wraps function-component props into this shape, so you read props.count() and the read subscribes.
ReactiveProps<P> carries a phantom RAW_PROPS brand so the JSX layer (ResolveProps) can recover the original P and translate the call-site type back to MaybeReactiveProps<P> for parents.
RawProps<R>
Recovers the raw prop shape P from a branded ReactiveProps<P>. For non-branded inputs returns the input unchanged. Mostly useful internally inside ResolveProps, but exported for advanced typing.
The JSX call-site translator wired into JSX.LibraryManagedAttributes. Decides what attributes the parent is allowed to write for any component (function or class):
You rarely need to use ResolveProps directly — it runs implicitly for every component JSX expression. Custom-element JSX types come from ElementProps<C> instead (different code path).
MaybeReactive<T>
A scalar value or a zero-arg getter that returns the value. Usually a signal or computed.
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param ― getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
@example
consta=signal(1);
constb=signal(2);
constsum=computed(() =>a() +b());
sum(); // → 3
a(10);
sum(); // → 12 (re-evaluated lazily)
computed } from"elements-kit/signals";
importtype {
typeMaybeReactive<T> =T|Computed<T>
A value that may be static or reactive. Accepts a plain T or a
zero-arg getter (() => T) — typically a signal or computed.
Used across the library anywhere a prop or attribute may be bound to
reactive state. Resolve with
Read: call with no arguments → returns the current value and
subscribes the active tracking context.
Write: call with a value → updates the signal and schedules
downstream effects if the value changed.
@example
constcount=signal(0);
count(); // → 0 (read)
count(1); // write – effects depending on count will re-run
count(); // → 1
signal(0);
const
constdouble: () =>number
double=
computed<number>(getter: (previousValue?:number|undefined) => number): () => number
Creates a lazily-evaluated computed value.
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param ― getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
@example
consta=signal(1);
constb=signal(2);
constsum=computed(() =>a() +b());
sum(); // → 3
a(10);
sum(); // → 12 (re-evaluated lazily)
computed(() =>
constcount: () =>number (+1overload)
count() *2);
const
consta:MaybeReactive<number>
a:
typeMaybeReactive<T> =T|Computed<T>
A value that may be static or reactive. Accepts a plain T or a
zero-arg getter (() => T) — typically a signal or computed.
Used across the library anywhere a prop or attribute may be bound to
reactive state. Resolve with