Types
Derive JSX prop types from components without hand-written declaration blocks.
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 have | Use |
|---|---|
HTMLElement subclass, want the full JSX shape | PropsOf<typeof Cls> |
| Class component with public instance fields | PropsOf<Cls<T>> |
Class component with constructor(props: P) | ConstructorParameters<typeof Cls>[0] (built-in) |
| Function component | PropsOf<typeof Fn> |
| Function component that accepts signals | Props<P> |
| Props converted to per-key getters | ComputedProps<P> |
| Hand-write a caller-facing shape (class ctor param) | MaybeReactiveProps<P> |
| Scalar value-or-getter | MaybeReactive<T> |
| Make optional keys required | Require<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 { , , type } from "elements-kit/attributes";
import { } from "elements-kit/signals";
import { } from "elements-kit/slot";
import type { } from "elements-kit/jsx-runtime";
@
class extends {
static []: <> = {
(: , ) { this. = ( ?? 0); },
};
declare static : { : <number> };
@() !: Node;
@() = 0;
}
type = <typeof >;
// {
// 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 { } from "elements-kit/jsx-runtime";
// 1. Class instance (lets a generic flow)
class <> {
: [] = [];
() { return null; }
}
type <> = <<>>;
// { each?: T[] }
// 2. Function component
const = (: { : string; ?: boolean }) => null;
type = <typeof >;
// { name: string; excited?: boolean }
// 3. Class constructor
class { = 0; () { return null; } }
type = <typeof >;
// { count?: number }
// 4. Custom-element constructor → the FULL JSX surface
// (attrs, prop:*, on:*,
// named slots, children)
class extends { = 0; }
type = <typeof >;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 { , } from "elements-kit/signals";
const = ({ : (0), : "n" });
.(); // 0 — subscribes
.(); // "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 { } from "elements-kit/jsx-runtime";
type = { : number; ?: string; : (: Event) => void };
type = <>;
// {
// 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 { , } from "elements-kit/signals";
import type { } from "elements-kit/signals";
const = (0);
const = (() => () * 2);
const : <number> = 5; // static
const : <number> = ; // signal
const : <number> = ; // computedResolve 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 { } from "elements-kit/jsx-runtime";
type = { ?: number; ?: string; ?: boolean };
type = <, "a" | "b">;
// { a: number; b: string; c?: boolean }See also
- Components — class components with
render(). - Custom Elements — authoring
HTMLElementsubclasses. - Signals (Reference) — the reactive primitives
MaybeReactivebuilds on.