# 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 have                                            | Use                                               |
| --------------------------------------------------- | ------------------------------------------------- |
| `HTMLElement` subclass, want the full JSX shape     | [`PropsOf<typeof Cls>`](#custom-element-surface)  |
| Class component with public instance fields         | [`PropsOf<Cls<T>>`](#propsofc)                    |
| Class component with `constructor(props: P)`        | `ConstructorParameters<typeof Cls>[0]` (built-in) |
| Function component                                  | [`PropsOf<typeof Fn>`](#propsofc)                 |
| Function component that accepts signals             | [`Props<P>`](#propsp)                             |
| Props converted to per-key getters                  | [`ComputedProps<P>`](#computedpropsp)             |
| Hand-write a caller-facing shape (class ctor param) | [`MaybeReactiveProps<P>`](#maybereactivepropsp)   |
| Scalar value-or-getter                              | [`MaybeReactive<T>`](#maybereactivet)             |
| Make optional keys required                         | [`Require<P, K>`](#requirep-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).

```tsx
import { attributes, ATTRIBUTES, type Attributes } from "elements-kit/attributes";
import { reactive } from "elements-kit/signals";
import { slot } from "elements-kit/slot";
import type { PropsOf } from "elements-kit/jsx-runtime";

@attributes
class XRange extends HTMLElement {
  static [ATTRIBUTES]: Attributes<XRange> = {
    min(this: XRange, v) { this.min = Number(v ?? 0); },
  };
  declare static events: { commit: CustomEvent<number> };
  @slot() label!: Node;

  @reactive() min = 0;
}

type XRangeProps = PropsOf<typeof 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](/custom-elements#using-your-element-outside-elements-kit).

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

```tsx
<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](https://github.com/wbettayeb/elements-kit/blob/main/src/jsx-runtime/properties.ts).

## `PropsOf<C>`

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

```ts
import type { PropsOf } from "elements-kit/jsx-runtime";

// 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 }

// 4. Custom-element constructor → the FULL JSX surface
//    (attrs, prop:*, on:*,
//     named slots, children)
class XRange extends HTMLElement { min = 0; }
type XRangeProps = PropsOf<typeof 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](#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>`](#maybereactivepropsp) 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`](/signals), or hand it to JSX, which subscribes when the value is reactive:

```tsx
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`](/components#opt-into-getter-props) — every key is a `Computed<T>`.

```ts
import { computedProps, signal } from "elements-kit/signals";

const props = computedProps({ count: signal(0), label: "n" });
props.count();   // 0 — subscribes
props.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:

```ts
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](/components#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):

```ts
import type { MaybeReactiveProps } from "elements-kit/jsx-runtime";

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>;
// }
```

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`.

```ts
import { signal, computed } from "elements-kit/signals";
import type { MaybeReactive } 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
const c: MaybeReactive<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>`](#propsp).

## `Require<P, K>`

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

```ts
import type { Require } from "elements-kit/jsx-runtime";

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

## See also

* [Components](/components) — class components with `render()`.
* [Custom Elements](/custom-elements) — authoring `HTMLElement` subclasses.
* [Signals (Reference)](/signals) — the reactive primitives `MaybeReactive` builds on.
