Skip to content

Types

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.

Pick a helper

You haveUse
HTMLElement subclass, want the full JSX shapeElementProps<typeof Cls>
Class component with public instance fieldsProps<Cls<T>>
Class component with constructor(props: P)ComponentProps<typeof Cls>
Function componentProps<typeof Fn>
Wrap caller-passed props in reactiveMaybeReactiveProps<P>
Function-component received props (auto-wrapped)ReactiveProps<P>
Recover raw P from a branded ReactiveProps<P>RawProps<R>
Translate any component to its JSX call-site shapeResolveProps<C, P>
Scalar value-or-getterMaybeReactive<T>
Manual prop bag → getters (non-JSX)resolveProps
Make optional keys requiredRequire<P, K>

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 — without MaybeReactive wrapping. Use this when you need the underlying property/handler types directly (testing, manual property assignment, deriving sub-types).

import {
function attributes<T extends abstract new (...args: any[]) => HTMLElement>(target: AttributeTarget<T>, context: ClassDecoratorContext<T>): AttributeDecorated<T>

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
class MyElement extends HTMLElement {
static [ATTRIBUTES] = {
count(this: MyElement, value: string | null) {
this.count = Number(value);
},
};
}

attributes
,
const ATTRIBUTES: typeof ATTRIBUTES

Static-field key used by the @attributes decorator (and

dispatchAttrChange

/

observedAttributes

) to locate the attribute handler map on a custom-element class.

@example

class MyElement extends HTMLElement {
static [ATTRIBUTES]: Attributes<MyElement> = {
name(value) { this.name = value ?? ""; },
};
}

ATTRIBUTES
, type
type Attributes<T> = {
[x: string]: AttrChangeHandler<T>;
}

Shape of the static [ATTRIBUTES] map: attribute name → handler bound to the element instance T.

Attributes
} from "elements-kit/attributes";
import {
function reactive<This extends object, Value>(source?: (self: This) => Signal<Value>): (_target: unknown, context: ClassFieldDecoratorContext<This, Value>) => (this: This, initialValue: Value) => Value

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

class Counter {
\@reactive() count: number = 0;
}
const counter = new Counter();
counter.count++; // Triggers reactivity
console.log(counter.count); // Subscribes to changes

@remarks

Equivalent to manually creating a private signal and getter/setter:

class Counter {
#count = signal(0);
get count() { return this.#count(); }
set count(value) { this.#count(value); }
}

reactive
} from "elements-kit/signals";
import {
const SLOTS: typeof SLOTS

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
,
class Slots<K extends string>

A keyed collection of slot instances. Slots are pre-created from the provided keys and lazily created on first access for unknown keys.

Slots
} from "elements-kit/slot";
import type {
type ElementProps<C extends AnyElementCtor> = BaseDOMAttrs & AttrsOf<C> & InstancePropsOf<C> & PropNamespacedOf<C> & EventsOf<C> & SlotsOf<C> & ChildrenOf<C>

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}.
  • Childrenchildren?: Child unless static children: never.
  • DOM attrs — the standard dom-expressions surface (class, style, ref, …).

@templateC — the custom-element class (constructor type).

@example

\@attributes
class XRange extends HTMLElement {
static [ATTRIBUTES]: Attributes<XRange> = { min(v) { this.min = +v! } };
declare static events: { commit: CustomEvent<number> };
[SLOTS] = Slots.new(["label"] as const);
\@reactive() min = 0;
}
type Props = ElementProps<typeof XRange>;
// {
// min?: MaybeReactive<number>;
// "prop:min"?: number;
// "on:commit"?: (e: CustomEvent<number>) => void;
// onCommit?: (e: CustomEvent<number>) => void;
// "slot:label"?: Child;
// children?: Child;
// // …plus ref, class, class:*, style, style:*, standard DOM events
// }

@seeProps for class-components / function components (no attr/event/slot synthesis).

ElementProps
} from "elements-kit/jsx-runtime";
@
function attributes<T extends abstract new (...args: any[]) => HTMLElement>(target: AttributeTarget<T>, context: ClassDecoratorContext<T>): AttributeDecorated<T>

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
class MyElement extends HTMLElement {
static [ATTRIBUTES] = {
count(this: MyElement, value: string | null) {
this.count = Number(value);
},
};
}

attributes
class
class XRange
XRange
extends
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.

MDN Reference

HTMLElement
{
static [
const ATTRIBUTES: typeof ATTRIBUTES

Static-field key used by the @attributes decorator (and

dispatchAttrChange

/

observedAttributes

) to locate the attribute handler map on a custom-element class.

@example

class MyElement extends HTMLElement {
static [ATTRIBUTES]: Attributes<MyElement> = {
name(value) { this.name = value ?? ""; },
};
}

ATTRIBUTES
]:
type Attributes<T> = {
[x: string]: AttrChangeHandler<T>;
}

Shape of the static [ATTRIBUTES] map: attribute name → handler bound to the element instance T.

Attributes
<
class XRange
XRange
> = {
function min(this: XRange, v: string | null): void
min
(
this: XRange
this
:
class XRange
XRange
,
v: string | null
v
) { this.
XRange.min: number
min
=
var Number: NumberConstructor
(value?: any) => number

An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.

Number
(
v: string | null
v
?? 0); },
};
declare static
XRange.events: {
commit: CustomEvent<number>;
}
events
: {
commit: CustomEvent<number>
commit
:
interface CustomEvent<T = any>

The CustomEvent interface can be used to attach custom data to an event generated by an application.

MDN Reference

CustomEvent
<number> };
[
const SLOTS: typeof SLOTS

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
] =
class Slots<K extends string>

A keyed collection of slot instances. Slots are pre-created from the provided keys and lazily created on first access for unknown keys.

Slots
.
Slots<K extends string>.new<"label">(keys: "label"[]): Slots<"label"> & {
readonly label: Slot & ((defaultContent?: PrimitiveNodeType) => DocumentFragment);
}

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

class Card extends HTMLElement {
// ✅ literal keys flow through — "header" | "footer"
[SLOTS] = Slots.new(["header", "footer"] as const);
}
// ❌ widens to string; no typed slot:* props
// [SLOTS] = Slots.new(["header", "footer"]);

new
(["label"] as
type const = ["label"]
const
);
@
reactive<object, unknown>(source?: ((self: object) => Signal<unknown>) | undefined): (_target: unknown, context: ClassFieldDecoratorContext<object, unknown>) => (this: object, initialValue: unknown) => unknown

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

class Counter {
\@reactive() count: number = 0;
}
const counter = new Counter();
counter.count++; // Triggers reactivity
console.log(counter.count); // Subscribes to changes

@remarks

Equivalent to manually creating a private signal and getter/setter:

class Counter {
#count = signal(0);
get count() { return this.#count(); }
set count(value) { this.#count(value); }
}

reactive
()
XRange.min: number
min
= 0;
}
type
type XRangeProps = BaseDOMAttrs & PropsOfInstance<XRange> & PropNamespacedOf<typeof XRange> & {
"on:commit"?: ((ev: CustomEvent<number>) => void) | undefined;
} & {
onCommit?: ((ev: CustomEvent<number>) => void) | undefined;
} & {
"slot:label"?: Child;
} & {
children?: Child;
}
XRangeProps
=
type ElementProps<C extends AnyElementCtor> = BaseDOMAttrs & AttrsOf<C> & InstancePropsOf<C> & PropNamespacedOf<C> & EventsOf<C> & SlotsOf<C> & ChildrenOf<C>

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}.
  • Childrenchildren?: Child unless static children: never.
  • DOM attrs — the standard dom-expressions surface (class, style, ref, …).

@templateC — the custom-element class (constructor type).

@example

\@attributes
class XRange extends HTMLElement {
static [ATTRIBUTES]: Attributes<XRange> = { min(v) { this.min = +v! } };
declare static events: { commit: CustomEvent<number> };
[SLOTS] = Slots.new(["label"] as const);
\@reactive() min = 0;
}
type Props = ElementProps<typeof XRange>;
// {
// min?: MaybeReactive<number>;
// "prop:min"?: number;
// "on:commit"?: (e: CustomEvent<number>) => void;
// onCommit?: (e: CustomEvent<number>) => void;
// "slot:label"?: Child;
// children?: Child;
// // …plus ref, class, class:*, style, style:*, standard DOM events
// }

@seeProps for class-components / function components (no attr/event/slot synthesis).

ElementProps
<typeof
class XRange
XRange
>;
// {
// min?: number; // raw property type
// "prop:min"?: number;
// "on:commit"?: (e: CustomEvent<number>) => void;
// onCommit?: (e: CustomEvent<number>) => void;
// "slot:label"?: Child;
// children?: Child;
// }

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-range min={0} /> // static
<x-range min={() => 0} /> // getter
<x-range min={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.

import type {
type Props<C> = (C extends abstract new (...args: any[]) => infer I ? MaybeReactiveProps<PropsOfInstance<I>> : C extends (props: infer P, ...rest: any[]) => any ? P extends object ? MaybeReactiveProps<RawProps<P>> : {} : MaybeReactiveProps<PropsOfInstance<C>>) & SlotsOf<C>

Props for any component — class or function. Wraps every non-function prop in

MaybeReactive

so callers may pass values or reactive getters.

Branches by input shape:

  • Class constructor (typeof Cls) → uses PropsOfInstance<InstanceType<Cls>>.
  • Function component ((props: P) => ...) → uses the first parameter.
  • Class instance (Cls<T>) → uses PropsOfInstance<Cls<T>> (useful when generics need to flow through — see the For example below).

Does not synthesize on:*, slot:*, or attribute surface. For custom elements that need those, use

ElementProps

.

@templateC — constructor, function, or instance.

@example

// 1. Class instance (lets a generic flow)
class For<T> { each: T[] = []; render() { return null } }
type ForProps<T> = Props<For<T>>;
// ↑ { each?: MaybeReactive<T[]> }
// 2. Function component
const Greeting = (_p: { name: string; excited?: boolean }) => null;
type GreetingProps = Props<typeof Greeting>;
// ↑ { name: MaybeReactive<string>; excited?: MaybeReactive<boolean> }
// 3. Class constructor
class Counter { count = 0; render() { return null } }
type CounterProps = Props<typeof Counter>;
// ↑ { count?: MaybeReactive<number> }

@seeElementProps — custom elements (HTMLElement subclasses).

@seeComponentProps — constructor-param-based class components.

@seeMaybeReactiveProps

Props
} from "elements-kit/jsx-runtime";
// 1. Class instance (lets a generic flow)
class
class For<T>
For
<
function (type parameter) T in For<T>
T
> {
For<T>.each: T[]
each
:
function (type parameter) T in For<T>
T
[] = [];
For<T>.render(): null
render
() { return null; }
}
type
type ForProps<T> = MaybeReactiveProps<PropsOfInstance<For<T>>> & {}
ForProps
<
function (type parameter) T in type ForProps<T>
T
> =
type Props<C> = (C extends abstract new (...args: any[]) => infer I ? MaybeReactiveProps<PropsOfInstance<I>> : C extends (props: infer P, ...rest: any[]) => any ? P extends object ? MaybeReactiveProps<RawProps<P>> : {} : MaybeReactiveProps<PropsOfInstance<C>>) & SlotsOf<C>

Props for any component — class or function. Wraps every non-function prop in

MaybeReactive

so callers may pass values or reactive getters.

Branches by input shape:

  • Class constructor (typeof Cls) → uses PropsOfInstance<InstanceType<Cls>>.
  • Function component ((props: P) => ...) → uses the first parameter.
  • Class instance (Cls<T>) → uses PropsOfInstance<Cls<T>> (useful when generics need to flow through — see the For example below).

Does not synthesize on:*, slot:*, or attribute surface. For custom elements that need those, use

ElementProps

.

@templateC — constructor, function, or instance.

@example

// 1. Class instance (lets a generic flow)
class For<T> { each: T[] = []; render() { return null } }
type ForProps<T> = Props<For<T>>;
// ↑ { each?: MaybeReactive<T[]> }
// 2. Function component
const Greeting = (_p: { name: string; excited?: boolean }) => null;
type GreetingProps = Props<typeof Greeting>;
// ↑ { name: MaybeReactive<string>; excited?: MaybeReactive<boolean> }
// 3. Class constructor
class Counter { count = 0; render() { return null } }
type CounterProps = Props<typeof Counter>;
// ↑ { count?: MaybeReactive<number> }

@seeElementProps — custom elements (HTMLElement subclasses).

@seeComponentProps — constructor-param-based class components.

@seeMaybeReactiveProps

Props
<
class For<T>
For
<
function (type parameter) T in type ForProps<T>
T
>>;
// { each?: MaybeReactive<T[]> }
// 2. Function component
const
const Greeting: (_p: {
name: string;
excited?: boolean;
}) => null
Greeting
= (
_p: {
name: string;
excited?: boolean;
}
_p
: {
name: string
name
: string;
excited?: boolean | undefined
excited
?: boolean }) => null;
type
type GreetingProps = MaybeReactiveProps<{
name: string;
excited?: boolean;
}> & {}
GreetingProps
=
type Props<C> = (C extends abstract new (...args: any[]) => infer I ? MaybeReactiveProps<PropsOfInstance<I>> : C extends (props: infer P, ...rest: any[]) => any ? P extends object ? MaybeReactiveProps<RawProps<P>> : {} : MaybeReactiveProps<PropsOfInstance<C>>) & SlotsOf<C>

Props for any component — class or function. Wraps every non-function prop in

MaybeReactive

so callers may pass values or reactive getters.

Branches by input shape:

  • Class constructor (typeof Cls) → uses PropsOfInstance<InstanceType<Cls>>.
  • Function component ((props: P) => ...) → uses the first parameter.
  • Class instance (Cls<T>) → uses PropsOfInstance<Cls<T>> (useful when generics need to flow through — see the For example below).

Does not synthesize on:*, slot:*, or attribute surface. For custom elements that need those, use

ElementProps

.

@templateC — constructor, function, or instance.

@example

// 1. Class instance (lets a generic flow)
class For<T> { each: T[] = []; render() { return null } }
type ForProps<T> = Props<For<T>>;
// ↑ { each?: MaybeReactive<T[]> }
// 2. Function component
const Greeting = (_p: { name: string; excited?: boolean }) => null;
type GreetingProps = Props<typeof Greeting>;
// ↑ { name: MaybeReactive<string>; excited?: MaybeReactive<boolean> }
// 3. Class constructor
class Counter { count = 0; render() { return null } }
type CounterProps = Props<typeof Counter>;
// ↑ { count?: MaybeReactive<number> }

@seeElementProps — custom elements (HTMLElement subclasses).

@seeComponentProps — constructor-param-based class components.

@seeMaybeReactiveProps

Props
<typeof
const Greeting: (_p: {
name: string;
excited?: boolean;
}) => null
Greeting
>;
// { name: MaybeReactive<string>; excited?: MaybeReactive<boolean> }
// 3. Class constructor
class
class Counter
Counter
{
Counter.count: number
count
= 0;
Counter.render(): null
render
() { return null; } }
type
type CounterProps = MaybeReactiveProps<PropsOfInstance<Counter>> & {}
CounterProps
=
type Props<C> = (C extends abstract new (...args: any[]) => infer I ? MaybeReactiveProps<PropsOfInstance<I>> : C extends (props: infer P, ...rest: any[]) => any ? P extends object ? MaybeReactiveProps<RawProps<P>> : {} : MaybeReactiveProps<PropsOfInstance<C>>) & SlotsOf<C>

Props for any component — class or function. Wraps every non-function prop in

MaybeReactive

so callers may pass values or reactive getters.

Branches by input shape:

  • Class constructor (typeof Cls) → uses PropsOfInstance<InstanceType<Cls>>.
  • Function component ((props: P) => ...) → uses the first parameter.
  • Class instance (Cls<T>) → uses PropsOfInstance<Cls<T>> (useful when generics need to flow through — see the For example below).

Does not synthesize on:*, slot:*, or attribute surface. For custom elements that need those, use

ElementProps

.

@templateC — constructor, function, or instance.

@example

// 1. Class instance (lets a generic flow)
class For<T> { each: T[] = []; render() { return null } }
type ForProps<T> = Props<For<T>>;
// ↑ { each?: MaybeReactive<T[]> }
// 2. Function component
const Greeting = (_p: { name: string; excited?: boolean }) => null;
type GreetingProps = Props<typeof Greeting>;
// ↑ { name: MaybeReactive<string>; excited?: MaybeReactive<boolean> }
// 3. Class constructor
class Counter { count = 0; render() { return null } }
type CounterProps = Props<typeof Counter>;
// ↑ { count?: MaybeReactive<number> }

@seeElementProps — custom elements (HTMLElement subclasses).

@seeComponentProps — constructor-param-based class components.

@seeMaybeReactiveProps

Props
<typeof
class Counter
Counter
>;
// { count?: MaybeReactive<number> }

For HTMLElement subclasses, Props<C> returns only the user’s public fields (no DOM surface). For plain classes, it returns all own fields.

ComponentProps<C>

For class components that accept props via the constructor parameter. Reads ConstructorParameters<C>[0]. Does not wrap reactively.

import type {
type ComponentProps<C extends ComponentClass<any>> = C extends ComponentClass<infer P extends Record<PropertyKey, unknown>> ? P extends object ? P : {} : {}

Props of a class component that receives them via its constructor: class Comp { constructor(props: P) }. Reads ConstructorParameters[0].

Use this when the component's props live on a constructor parameter rather than on public instance fields. For instance-field components, use

Props

.

@templateC — the class constructor type (e.g. typeof Card).

@example

class Card {
constructor(public props: { title: string; children?: Child }) {}
render() { return <div>{this.props.title}</div>; }
}
type P = ComponentProps<typeof Card>;
// { title: string; children?: Child }

@seeProps

ComponentProps
} from "elements-kit/jsx-runtime";
import type {
type Child = string | number | bigint | boolean | symbol | RegExp | Node | Date | Element | DocumentFragment | AnyFn | Child[] | null | undefined

Anything that can appear as a JSX child.

Child
} from "elements-kit/jsx-runtime";
class
class Card
Card
{
constructor(public
Card.props: {
title: string;
children?: Child;
}
props
: {
title: string
title
: string;
children?: Child
children
?:
type Child = string | number | bigint | boolean | symbol | RegExp | Node | Date | Element | DocumentFragment | AnyFn | Child[] | null | undefined

Anything that can appear as a JSX child.

Child
}) {}
Card.render(): JSX$1.Element
render
() { return <div>{this.
Card.props: {
title: string;
children?: Child;
}
props
.
title: string
title
}</div>; }
}
type
type P = {
title: string;
children?: Child;
}
P
=
type ComponentProps<C extends ComponentClass<any>> = C extends ComponentClass<infer P extends Record<PropertyKey, unknown>> ? P extends object ? P : {} : {}

Props of a class component that receives them via its constructor: class Comp { constructor(props: P) }. Reads ConstructorParameters[0].

Use this when the component's props live on a constructor parameter rather than on public instance fields. For instance-field components, use

Props

.

@templateC — the class constructor type (e.g. typeof Card).

@example

class Card {
constructor(public props: { title: string; children?: Child }) {}
render() { return <div>{this.props.title}</div>; }
}
type P = ComponentProps<typeof Card>;
// { title: string; children?: Child }

@seeProps

ComponentProps
<typeof
class Card
Card
>;
// { title: string; children?: Child }

Use Props<C> instead when props live on public instance fields.

MaybeReactiveProps<P>

Caller-facing — wrap every prop in MaybeReactive. Describes what parents pass into a component (a value or a signal/computed per key).

import type {
type MaybeReactiveProps<P> = { [K in keyof P]: undefined extends P[K] ? undefined | MaybeReactive<Exclude<P[K], undefined>> : MaybeReactive<P[K]>; }

Wrap every prop in

MaybeReactive

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.

@templateP — source prop object type.

@example

type Raw = { count: number; label?: string; onClick: (e: Event) => void };
type Wrapped = MaybeReactiveProps<Raw>;
// {
// count: MaybeReactive<number>;
// label?: MaybeReactive<string>;
// onClick: MaybeReactive<(e: Event) => void>; // computed handlers OK
// }

@seeMaybeReactive

@seeProps

MaybeReactiveProps
} from "elements-kit/jsx-runtime";
type
type Raw = {
count: number;
label?: string;
onClick: (e: Event) => void;
}
Raw
= {
count: number
count
: number;
label?: string | undefined
label
?: string;
onClick: (e: Event) => void
onClick
: (
e: Event
e
:
interface Event

The Event interface represents an event which takes place on an EventTarget.

MDN Reference

Event
) => void };
type
type Wrapped = {
count: MaybeReactive<number>;
label?: MaybeReactive<string> | undefined;
onClick: MaybeReactive<(e: Event) => void>;
}
Wrapped
=
type MaybeReactiveProps<P> = { [K in keyof P]: undefined extends P[K] ? undefined | MaybeReactive<Exclude<P[K], undefined>> : MaybeReactive<P[K]>; }

Wrap every prop in

MaybeReactive

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.

@templateP — source prop object type.

@example

type Raw = { count: number; label?: string; onClick: (e: Event) => void };
type Wrapped = MaybeReactiveProps<Raw>;
// {
// count: MaybeReactive<number>;
// label?: MaybeReactive<string>;
// onClick: MaybeReactive<(e: Event) => void>; // computed handlers OK
// }

@seeMaybeReactive

@seeProps

MaybeReactiveProps
<
type Raw = {
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.

import type { ReactiveProps } from "elements-kit/jsx-runtime";
function Counter(props: ReactiveProps<{ count: number }>) {
return <p>{() => props.count()}</p>;
}

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.

import type { RawProps, ReactiveProps } from "elements-kit/jsx-runtime";
type Branded = ReactiveProps<{ name: string }>;
type Raw = RawProps<Branded>; // { name: string }
type Plain = RawProps<{ a: 1 }>; // { a: 1 } (passes through)

ResolveProps<C, P>

The JSX call-site translator wired into JSX.LibraryManagedAttributes. Decides what attributes the parent is allowed to write for any component (function or class):

  • Branded ReactiveProps<P> (function components) → MaybeReactiveProps<P>.
  • Empty constructor param (instance-field classes) → Props<C>.
  • Otherwise (e.g. For<T> with constructor(_props?: ForProps<T>)) → pass through, preserving generic inference.

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.

import {
function signal<T>(): Updater<T> & Computed<T> (+1 overload)

Creates a mutable reactive signal.

  • 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

const count = signal(0);
count(); // → 0 (read)
count(1); // write – effects depending on count will re-run
count(); // → 1

signal
,
function computed<T>(getter: (previousValue?: T) => T): () => T

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.

@paramgetter - Pure function deriving a value from other reactive sources. Receives the previous value as an optional optimisation hint.

@example

const a = signal(1);
const b = signal(2);
const sum = computed(() => a() + b());
sum(); // → 3
a(10);
sum(); // → 12 (re-evaluated lazily)

computed
} from "elements-kit/signals";
import type {
type MaybeReactive<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

resolve

, detect with

isReactive

.

@templateT — the value type.

@example

import { signal, computed } from "elements-kit/signals";
const count = signal(0);
const double = computed(() => count() * 2);
const a: MaybeReactive<number> = 5; // static
const b: MaybeReactive<number> = count; // signal (getter)
const c: MaybeReactive<number> = double; // computed (getter)

MaybeReactive
} from "elements-kit/signals";
const
const count: Updater<number> & Computed<number>
count
=
signal<number>(initialValue: number): Updater<number> & Computed<number> (+1 overload)

Creates a mutable reactive signal.

  • 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

const count = signal(0);
count(); // → 0 (read)
count(1); // write – effects depending on count will re-run
count(); // → 1

signal
(0);
const
const double: () => 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.

@paramgetter - Pure function deriving a value from other reactive sources. Receives the previous value as an optional optimisation hint.

@example

const a = signal(1);
const b = signal(2);
const sum = computed(() => a() + b());
sum(); // → 3
a(10);
sum(); // → 12 (re-evaluated lazily)

computed
(() =>
const count: () => number (+1 overload)
count
() * 2);
const
const a: MaybeReactive<number>
a
:
type MaybeReactive<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

resolve

, detect with

isReactive

.

@templateT — the value type.

@example

import { signal, computed } from "elements-kit/signals";
const count = signal(0);
const double = computed(() => count() * 2);
const a: MaybeReactive<number> = 5; // static
const b: MaybeReactive<number> = count; // signal (getter)
const c: MaybeReactive<number> = double; // computed (getter)

MaybeReactive
<number> = 5; // static
const
const b: MaybeReactive<number>
b
:
type MaybeReactive<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

resolve

, detect with

isReactive

.

@templateT — the value type.

@example

import { signal, computed } from "elements-kit/signals";
const count = signal(0);
const double = computed(() => count() * 2);
const a: MaybeReactive<number> = 5; // static
const b: MaybeReactive<number> = count; // signal (getter)
const c: MaybeReactive<number> = double; // computed (getter)

MaybeReactive
<number> =
const count: Updater<number> & Computed<number>
count
; // signal
const
const c: MaybeReactive<number>
c
:
type MaybeReactive<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

resolve

, detect with

isReactive

.

@templateT — the value type.

@example

import { signal, computed } from "elements-kit/signals";
const count = signal(0);
const double = computed(() => count() * 2);
const a: MaybeReactive<number> = 5; // static
const b: MaybeReactive<number> = count; // signal (getter)
const c: MaybeReactive<number> = double; // computed (getter)

MaybeReactive
<number> =
const double: () => number
double
; // computed

Resolve with resolve(value), detect with isReactive(value). For a whole props object in a function component, see resolveProps.

Require<P, K>

Promote specified keys of P to required. Leaves the rest unchanged.

import type {
type Require<P, K extends keyof P> = { [X in K]-?: P[X]; } & Omit<P, K>

Promote keys K of P to required; leave the rest unchanged.

@templateP — the prop object type.

@templateK — the keys to make required.

@example

type Optional = { a?: number; b?: string; c?: boolean };
type AB = Require<Optional, "a" | "b">;
// { a: number; b: string; c?: boolean }

Require
} from "elements-kit/jsx-runtime";
type
type Optional = {
a?: number;
b?: string;
c?: boolean;
}
Optional
= {
a?: number | undefined
a
?: number;
b?: string | undefined
b
?: string;
c?: boolean | undefined
c
?: boolean };
type
type AB = {
a: number;
b: string;
} & Omit<Optional, "a" | "b">
AB
=
type Require<P, K extends keyof P> = { [X in K]-?: P[X]; } & Omit<P, K>

Promote keys K of P to required; leave the rest unchanged.

@templateP — the prop object type.

@templateK — the keys to make required.

@example

type Optional = { a?: number; b?: string; c?: boolean };
type AB = Require<Optional, "a" | "b">;
// { a: number; b: string; c?: boolean }

Require
<
type Optional = {
a?: number;
b?: string;
c?: boolean;
}
Optional
, "a" | "b">;
// { a: number; b: string; c?: boolean }

See also