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 shapePropsOf<typeof Cls>
Class component with public instance fieldsPropsOf<Cls<T>>
Class component with constructor(props: P)ConstructorParameters<typeof Cls>[0] (built-in)
Function componentPropsOf<typeof Fn>
Function component that accepts signalsProps<P>
Props converted to per-key gettersComputedProps<P>
Hand-write a caller-facing shape (class ctor param)MaybeReactiveProps<P>
Scalar value-or-getterMaybeReactive<T>
Make optional keys requiredRequire<P, K>

All type helpers are re-exported from elements-kit/jsx-runtime. MaybeReactive also lives in elements-kit/signals.

Custom-element surface

PropsOf<typeof Cls> on a custom-element class returns the full raw JSX surface: attributes, flat properties, prop:*, events, slots, and children β€” without MaybeReactive wrapping. Use this when you need the underlying property/handler types directly (testing, 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 {
function slot(): <This extends object, V extends SlotContent | null>(_target: unknown, context: ClassFieldDecoratorContext<This, V>) => (this: This, initialValue: V) => V

Field decorator declaring a slot as a plain property backed by a

Slot

. Reading returns the slot's region (slot.get() β€” a fragment to append into any template, elements-kit JSX or not); assigning fills it (null clears). Values follow native append() semantics β€” a Node, a string, or an array of them. Assignment buffers before mount, so consumers can set content before the element renders.

The getter is EFFECTFUL: it mounts the markers on first read and extracts current content on later reads (the re-render semantic of

Slot.get

). Read it to PLACE the region, not to inspect it.

Declare the field as

SlotContent

to accept the full native append() surface (Node | string | array); narrow it to Node when the slot only ever holds an element.

@example

class Card extends HTMLElement {
\@slot() header!: SlotContent;
render() {
return <header>{this.header}</header>; // or root.append(card.header)
}
}
// consumers β€” any framework, or none:
card.header = document.createElement("h1"); // Node
card.header = "plain text"; // native append() content
card.header = null; // clear

slot
} from "elements-kit/slot";
import type {
type PropsOf<T extends JSX$1.ElementType | JSX$1.ElementClass | AnyElementCtor> = T extends AnyElementCtor ? ElementProps<T> : T extends JSX$1.ElementType | JSX$1.ElementClass ? ComponentProps<T> : never

Props for any component β€” class or function.

The combination of the two specialised helpers:

  • Custom-element constructor (typeof Cls, Cls extends HTMLElement) β†’ ElementProps<Cls> β€” the full JSX surface (attrs, prop:*, on:*,children).
  • Everything else (function component, class component ctor or instance) β†’ ComponentProps<T> β€” the raw prop shape.

@template ― T β€” constructor, function, or instance.

@example

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

PropsOf
} 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> };
@
function slot(): <This extends object, V extends SlotContent | null>(_target: unknown, context: ClassFieldDecoratorContext<This, V>) => (this: This, initialValue: V) => V

Field decorator declaring a slot as a plain property backed by a

Slot

. Reading returns the slot's region (slot.get() β€” a fragment to append into any template, elements-kit JSX or not); assigning fills it (null clears). Values follow native append() semantics β€” a Node, a string, or an array of them. Assignment buffers before mount, so consumers can set content before the element renders.

The getter is EFFECTFUL: it mounts the markers on first read and extracts current content on later reads (the re-render semantic of

Slot.get

). Read it to PLACE the region, not to inspect it.

Declare the field as

SlotContent

to accept the full native append() surface (Node | string | array); narrow it to Node when the slot only ever holds an element.

@example

class Card extends HTMLElement {
\@slot() header!: SlotContent;
render() {
return <header>{this.header}</header>; // or root.append(card.header)
}
}
// consumers β€” any framework, or none:
card.header = document.createElement("h1"); // Node
card.header = "plain text"; // native append() content
card.header = null; // clear

slot
()
XRange.label: Node
label
!:
interface Node

The DOM Node interface is an abstract base class upon which many other DOM API objects are based, thus letting those object types be used similarly and often interchangeably. As an abstract class, there is no such thing as a plain Node object. All objects that implement Node functionality are based on one of its subclasses. Most notable are Document, Element, and DocumentFragment.

MDN Reference

Node
;
@
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 & {
min?: number | undefined;
label?: Node | undefined;
} & PropNamespacedOf<typeof XRange> & JsxEventsOf<typeof XRange> & {
children?: Children;
}
XRangeProps
=
type PropsOf<T extends JSX$1.ElementType | JSX$1.ElementClass | AnyElementCtor> = T extends AnyElementCtor ? ElementProps<T> : T extends JSX$1.ElementType | JSX$1.ElementClass ? ComponentProps<T> : never

Props for any component β€” class or function.

The combination of the two specialised helpers:

  • Custom-element constructor (typeof Cls, Cls extends HTMLElement) β†’ ElementProps<Cls> β€” the full JSX surface (attrs, prop:*, on:*,children).
  • Everything else (function component, class component ctor or instance) β†’ ComponentProps<T> β€” the raw prop shape.

@template ― T β€” constructor, function, or instance.

@example

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

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

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.

For consuming a custom element outside elements-kit JSX (React, Svelte, Vue, vanilla DOM), use the framework-agnostic extractors from elements-kit/custom-elements instead β€” PropertiesOf, AttributesOf, EventsOf β€” see Custom Elements.

At the JSX call site, the runtime widens every key to value-or-getter, so parents may pass static values or signals/computed:

<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:, ref) are layered on every intrinsic element via src/jsx-runtime/properties.ts.

PropsOf<C>

Unified helper. Works on class instances, class constructors, function components, and custom-element constructors.

import type {
type PropsOf<T extends JSX$1.ElementType | JSX$1.ElementClass | AnyElementCtor> = T extends AnyElementCtor ? ElementProps<T> : T extends JSX$1.ElementType | JSX$1.ElementClass ? ComponentProps<T> : never

Props for any component β€” class or function.

The combination of the two specialised helpers:

  • Custom-element constructor (typeof Cls, Cls extends HTMLElement) β†’ ElementProps<Cls> β€” the full JSX surface (attrs, prop:*, on:*,children).
  • Everything else (function component, class component ctor or instance) β†’ ComponentProps<T> β€” the raw prop shape.

@template ― T β€” constructor, function, or instance.

@example

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

PropsOf
} 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> = {
each?: T[] | undefined;
}
ForProps
<
function (type parameter) T in type ForProps<T>
T
> =
type PropsOf<T extends JSX$1.ElementType | JSX$1.ElementClass | AnyElementCtor> = T extends AnyElementCtor ? ElementProps<T> : T extends JSX$1.ElementType | JSX$1.ElementClass ? ComponentProps<T> : never

Props for any component β€” class or function.

The combination of the two specialised helpers:

  • Custom-element constructor (typeof Cls, Cls extends HTMLElement) β†’ ElementProps<Cls> β€” the full JSX surface (attrs, prop:*, on:*,children).
  • Everything else (function component, class component ctor or instance) β†’ ComponentProps<T> β€” the raw prop shape.

@template ― T β€” constructor, function, or instance.

@example

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

PropsOf
<
class For<T>
For
<
function (type parameter) T in type ForProps<T>
T
>>;
// { each?: 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 = {
name: string;
excited?: boolean;
}
GreetingProps
=
type PropsOf<T extends JSX$1.ElementType | JSX$1.ElementClass | AnyElementCtor> = T extends AnyElementCtor ? ElementProps<T> : T extends JSX$1.ElementType | JSX$1.ElementClass ? ComponentProps<T> : never

Props for any component β€” class or function.

The combination of the two specialised helpers:

  • Custom-element constructor (typeof Cls, Cls extends HTMLElement) β†’ ElementProps<Cls> β€” the full JSX surface (attrs, prop:*, on:*,children).
  • Everything else (function component, class component ctor or instance) β†’ ComponentProps<T> β€” the raw prop shape.

@template ― T β€” constructor, function, or instance.

@example

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

PropsOf
<typeof
const Greeting: (_p: {
name: string;
excited?: boolean;
}) => null
Greeting
>;
// { name: string; excited?: boolean }
// 3. Class constructor
class
class Counter
Counter
{
Counter.count: number
count
= 0;
Counter.render(): null
render
() { return null; } }
type
type CounterProps = {
count?: number | undefined;
}
CounterProps
=
type PropsOf<T extends JSX$1.ElementType | JSX$1.ElementClass | AnyElementCtor> = T extends AnyElementCtor ? ElementProps<T> : T extends JSX$1.ElementType | JSX$1.ElementClass ? ComponentProps<T> : never

Props for any component β€” class or function.

The combination of the two specialised helpers:

  • Custom-element constructor (typeof Cls, Cls extends HTMLElement) β†’ ElementProps<Cls> β€” the full JSX surface (attrs, prop:*, on:*,children).
  • Everything else (function component, class component ctor or instance) β†’ ComponentProps<T> β€” the raw prop shape.

@template ― T β€” constructor, function, or instance.

@example

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

PropsOf
<typeof
class Counter
Counter
>;
// { count?: number }
// 4. Custom-element constructor β†’ the FULL JSX surface
// (attrs, prop:*, on:*,
// named slots, children)
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
{
XRange.min: number
min
= 0; }
type
type XRangeProps = BaseDOMAttrs & {} & {
min?: number | undefined;
} & PropNamespacedOf<typeof XRange> & JsxEventsOf<typeof XRange> & {
children?: Children;
}
XRangeProps
=
type PropsOf<T extends JSX$1.ElementType | JSX$1.ElementClass | AnyElementCtor> = T extends AnyElementCtor ? ElementProps<T> : T extends JSX$1.ElementType | JSX$1.ElementClass ? ComponentProps<T> : never

Props for any component β€” class or function.

The combination of the two specialised helpers:

  • Custom-element constructor (typeof Cls, Cls extends HTMLElement) β†’ ElementProps<Cls> β€” the full JSX surface (attrs, prop:*, on:*,children).
  • Everything else (function component, class component ctor or instance) β†’ ComponentProps<T> β€” the raw prop shape.

@template ― T β€” constructor, function, or instance.

@example

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

PropsOf
<typeof
class XRange
XRange
>;

For HTMLElement instances, PropsOf<C> returns only the user’s public fields (no DOM surface); the constructor form returns the full custom-element surface (attributes / events / slots). For plain classes, it returns all own fields.

PropsOf accepts components only β€” a plain prop shape like PropsOf<{ name: string }> is a type error. It returns the raw shapes; the JSX call site widens each key to value-or-getter automatically. Note PropsOf makes every instance field optional (class fields carry initializers, so there is no requiredness signal to preserve).

For class components that accept props via the constructor parameter (class Card { constructor(props: P) }), use TypeScript’s built-in ConstructorParameters<typeof Card>[0] β€” the param shape passes through to the JSX call site unchanged.

Props<P>

Declares that every prop accepts a plain value or a reactive source. It is an alias for MaybeReactiveProps<P> under the name function components reach for.

Function-component props are never transformed by the runtime β€” they arrive exactly as the caller wrote them β€” so this type is how a component opts in to receiving signals. Read a key with resolve, or hand it to JSX, which subscribes when the value is reactive:

import { resolve } from "elements-kit/signals";
import type { Props } from "elements-kit/jsx-runtime";
function Counter(props: Props<{ count: number }>) {
return <p>{props.count}</p>; // live when a signal was passed
}
function Doubler(props: Props<{ count: number }>) {
return <p>{() => resolve(props.count) * 2}</p>;
}

Omit it and declare plain value types when a component takes static props only β€” callers then cannot pass a signal, and the component never has to handle one.

ComputedProps<P>

The mirror of Props<P>. Props<P> describes what a caller may pass (value or source); ComputedProps<P> describes what a body reads after converting with computedProps β€” every key is a Computed<T>.

import {
function computedProps<P extends object>(raw: P & NoArgFnProps<P>): ComputedProps<Unwrap<P>>

Turn props into a bag of per-key getters, so a body reads one shape no matter which form the caller passed. Opt-in: the JSX runtime hands function components their props untouched.

Every key is callable, including one the caller omitted. A getter is always truthy, so defaults go on the call: props.excited() ?? "…". Keys are branded sources, so they keep working when forwarded to a child component.

Function props are the limit: a prop taking arguments is rejected, and a zero-arg one types as its return value. Read those off the raw props.

@example

const count = signal(0);
const props = computedProps({ count, label: "n" });
props.count(); // 0 β€” subscribes to count
props.label(); // "n"

computedProps
,
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
} from "elements-kit/signals";
const
const props: ComputedProps<Unwrap<{
count: Updater<number> & Computed<number>;
label: string;
}>>
props
=
computedProps<{
count: Updater<number> & Computed<number>;
label: string;
}>(raw: {
count: Updater<number> & Computed<number>;
label: string;
} & NoArgFnProps<{
count: Updater<number> & Computed<number>;
label: string;
}>): ComputedProps<Unwrap<{
count: Updater<number> & Computed<number>;
label: string;
}>>

Turn props into a bag of per-key getters, so a body reads one shape no matter which form the caller passed. Opt-in: the JSX runtime hands function components their props untouched.

Every key is callable, including one the caller omitted. A getter is always truthy, so defaults go on the call: props.excited() ?? "…". Keys are branded sources, so they keep working when forwarded to a child component.

Function props are the limit: a prop taking arguments is rejected, and a zero-arg one types as its return value. Read those off the raw props.

@example

const count = signal(0);
const props = computedProps({ count, label: "n" });
props.count(); // 0 β€” subscribes to count
props.label(); // "n"

computedProps
({
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),
label: string
label
: "n" });
const props: ComputedProps<Unwrap<{
count: Updater<number> & Computed<number>;
label: string;
}>>
props
.
count: () => number
count
(); // 0 β€” subscribes
const props: ComputedProps<Unwrap<{
count: Updater<number> & Computed<number>;
label: string;
}>>
props
.
label: () => string
label
(); // "n"

Optional keys lose their ?. The bag hands back a getter for every key, including one the caller omitted, so the getter is never missing β€” only its result is:

type Bag = ComputedProps<{ name: string; excited?: boolean }>;
// {
// readonly name: Computed<string>;
// readonly excited: Computed<boolean | undefined>; // present, not optional
// }

That is what lets you write props.excited() with no ?.. It also means a key is always truthy, so defaults go on the call β€” props.excited() ?? false, never props.excited ?? false.

computedProps infers its shape from the argument and unwraps reactive keys in the result, so a signal-backed key reads as its value. Function props are the limit of that inference: one taking arguments is rejected, and a zero-arg one types as its return value. See function props.

MaybeReactiveProps<P>

Wrap every prop in MaybeReactive β€” the same type as Props<P>, under its descriptive name. The JSX checker applies it automatically to intrinsic and custom-element attributes, so you rarely write it there. Name it directly when hand-writing a call-site shape β€” the main case is a class component’s constructor param (the shape passes through to the JSX call site verbatim, like For’s):

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

Caller-facing wrap: each key accepts a plain value OR a reactive getter. Name it when typing a call-site shape by hand (e.g. a class component's constructor param, like For's); the JSX checker applies it to intrinsic and custom-element props. Function-typed props are wrapped too (Computed<F> is zero-arg, so TS still picks the handler signature by arity for inline arrows). Signal<F> must never be added explicitly β€” its one-arg Updater half would collapse inline arrow params to implicit any.

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]>; }

Caller-facing wrap: each key accepts a plain value OR a reactive getter. Name it when typing a call-site shape by hand (e.g. a class component's constructor param, like For's); the JSX checker applies it to intrinsic and custom-element props. Function-typed props are wrapped too (Computed<F> is zero-arg, so TS still picks the handler signature by arity for inline arrows). Signal<F> must never be added explicitly β€” its one-arg Updater half would collapse inline arrow params to implicit any.

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

Unlike PropsOf, it preserves required keys.

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.

@param ― getter - 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

.

@template ― T β€” 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.

@param ― getter - 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

.

@template ― T β€” 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

.

@template ― T β€” 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

.

@template ― T β€” 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). To accept either form on every key of a function component’s props, see Props<P>.

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.

@template ― P β€” the prop object type.

@template ― K β€” 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.

@template ― P β€” the prop object type.

@template ― K β€” 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