TypeScript Support
How RilayKit's type propagation system provides full autocompletion and compile-time safety from component registry to rendered fields.
RilayKit is built around type propagation: each registered component extends the catalog's { type → props } map on the ril generic, so form definitions are checked against your components at compile time.
Type Propagation
Step 1: Component Registration
Each .component() call extends the generic. TProps is inferred from the renderer's ComponentRenderContext<TProps> parameter (or from propsSchema):
import { ril, type ComponentRenderContext } from 'rilaykit';
interface TextInputProps {
label: string;
placeholder?: string;
}
interface SelectInputProps {
label: string;
options: Array<{ value: string; label: string }>;
}
function TextInput(ctx: ComponentRenderContext<TextInputProps>) { /* ... */ }
function SelectInput(ctx: ComponentRenderContext<SelectInputProps>) { /* ... */ }
export const rilay = ril
.create()
.component('text', { name: 'Text Input', renderer: TextInput })
.component('select', { name: 'Select Input', renderer: SelectInput });
// rilay: ril<{ text: TextInputProps; select: SelectInputProps }>Step 2: Form Building
In .add(), type autocompletes from the registered keys and props narrows to that component's own props — per argument, so a variadic .add(a, b) checks each field against its own type:
import { form, required, email } from 'rilaykit';
import { rilay } from '@/lib/rilay';
const loginForm = form.create(rilay, 'login')
.add({
id: 'email',
type: 'text', // autocompletes: 'text' | 'select'
props: { label: 'Email' }, // checked against Partial<TextInputProps>
validation: { validate: [required(), email()] },
});props is typed Partial<TProps>: unknown keys and wrong value types are compile errors, but a missing required prop is not. Register a propsSchema on the component to catch that at runtime (rilay.validateProps(type, props), or compileForm(..., { validateProps: true })).
Error Prevention at Compile Time
form.create(rilay, 'test').add({
type: 'checkbox',
// Error: '"checkbox"' is not assignable to '"text" | "select"'
props: { label: 'Accept' },
});
form.create(rilay, 'test').add({
id: 'email',
type: 'text',
props: {
label: 'Email',
options: [], // Error: 'options' does not exist on Partial<TextInputProps>
},
});Immutable API
ril is immutable — .component() returns a new instance with an extended type. Chain calls or assign the result; a discarded return value registers nothing:
const base = ril.create();
base.component('text', { renderer: TextInput });
// base is unchanged — 'text' was registered on a discarded instance
const rilay = ril.create().component('text', { renderer: TextInput });
// rilay: ril<{ text: TextInputProps }>Workflows
Step definitions are type-checked too — formConfig accepts a built FormConfiguration or a form builder:
import { flow } from 'rilaykit';
const workflow = flow.create(rilay, 'onboarding', 'Onboarding')
.step({
id: 'personal',
title: 'Personal Information',
formConfig: loginForm, // type-checked
});Best Practices
- Define an explicit props interface per component and annotate the renderer's
ComponentRenderContext<TProps>— inference flows from there - Export a single shared
rilayinstance from a central file (e.g.lib/rilay.ts) - Let TypeScript infer the generics — don't specify them manually
- Enable
strictmode intsconfig.json