Introduction
RilayKit is a config-driven form and workflow engine for React. When your forms become data - not JSX - you unlock serialization, multi-tenancy, visual builders, and true UI/logic separation.
rilaykit
The schema-first form and workflow engine for React.
Is RilayKit for you?
RilayKit is not a React Hook Form replacement for simple forms.
Start here
Building a login, contact, or settings form? → Use React Hook Form + Zod.
RilayKit is for you if you're building one of these:
✅ You need RilayKit when:
- Forms are generated dynamically from server config or database schemas
- Multi-tenant SaaS where each client customizes form fields and workflows
- Visual form builder for non-technical users
- Complex multi-step workflows with cross-step conditional logic (onboarding, KYC, checkout)
- Same logic, multiple UIs — one form schema across design systems
- AI-driven UI — an agent that renders forms and flows as tool calls
- Form versioning & A/B testing — store forms as data, diff them, roll back
❌ You don't need RilayKit when:
- Standard CRUD forms with static field sets
- Forms defined once that never change dynamically
- Simple wizard flows with no cross-step dependencies
The tipping point
Scenario: multi-step onboarding where step visibility depends on earlier answers, and a step is skipped if revenue < $50k. With React Hook Form this means a hand-rolled state machine, useEffect cross-step wiring, and 200+ lines of conditional rendering. With RilayKit it's configuration:
const basicsForm = rilay.form('basics')
.add({ id: 'userType', type: 'select', props: { options: ['freelance', 'business'] } })
.add({ id: 'revenue', type: 'number', props: { label: 'Annual Revenue' } })
.build();
const onboarding = rilay.flow('onboarding', 'User Onboarding')
.step({ id: 'basics', title: 'Basic Information', formConfig: basicsForm })
.step({
id: 'business',
title: 'Business Details',
formConfig: businessForm,
conditions: {
visible: when('basics.userType').equals('business').build(),
skippable: when('basics.revenue').lessThan(50000).build(),
},
})
.step({
id: 'freelance',
title: 'Freelance Details',
formConfig: freelanceForm,
conditions: { visible: when('basics.userType').equals('freelance').build() },
});
// Serialize to DB, version control, A/B test (toJSON lives on the builder)
const json = onboarding.toJSON();
const config = onboarding.build();Business logic lives in declarative configuration — data you can store, diff, version, and generate.
Quick example
import { ril, required, email, when } from 'rilaykit';
import { Form } from 'rilaykit/react';
// 1. Register your components once (shadcn, MUI, custom — your choice)
const rilay = ril.create()
.component('input', { renderer: YourInput })
.component('select', { renderer: YourSelect });
// 2. Define forms as data (not JSX)
const signupForm = rilay.form('signup')
.add({ id: 'email', type: 'input', validation: { validate: [required(), email()] } })
.add({ id: 'plan', type: 'select', props: { options: ['free', 'pro', 'enterprise'] } })
.add({
id: 'company',
type: 'input',
conditions: { visible: when('plan').in(['pro', 'enterprise']).build() },
})
.build();
// 3. Render anywhere with full type safety
<Form of={signupForm} onSubmit={handleSubmit}>
<Form.Field id="email" />
<Form.Field id="plan" />
<Form.Field id="company" /> {/* auto-hidden unless plan is pro/enterprise */}
</Form>RilayKit is fully headless — it manages state, validation, and logic. You own the components, the markup, and the styling.
React Hook Form vs RilayKit
| Criteria | React Hook Form | RilayKit |
|---|---|---|
| Learning curve | ✅ Simple, familiar patterns | ⚠️ Steeper (new concepts) |
| Simple forms | ✅ Perfect fit | ❌ Overkill |
| Multi-step with conditions | ⚠️ Custom state machine | ✅ Built-in workflow engine |
| Forms as data | ❌ Not possible | ✅ Core feature (.toJSON()) |
| Dynamic form generation | ❌ Challenging | ✅ Designed for it |
| Multiple design systems | ⚠️ Requires refactoring | ✅ Swap renderers, logic stays |
| Ecosystem maturity | ✅ Huge, battle-tested | ⚠️ Young but growing |
| Type safety | ✅ Good (with Zod/TS) | ✅ Excellent (built-in propagation) |
Rule of thumb: if you're not sure you need RilayKit, you probably don't. Start with React Hook Form; migrate when you hit the limitations above.
What makes RilayKit different
1. Forms are data, not JSX
Your form definition is a serializable data structure:
const pricingForm = rilay.form('pricing')
.add({ id: 'plan', type: 'select', props: { options: ['free', 'pro'] } });
const json = pricingForm.toJSON(); // serialize from the builder
await db.forms.save(json);
// Load it later, even from a different server
const loaded = rilay.form().fromJSON(json).build();Multi-tenant customization, visual builders, A/B testing, and versioning all fall out of this.
2. Type propagation end-to-end
Register a component once and TypeScript propagates its types everywhere:
const rilay = ril.create()
.component('input', { renderer: Input }); // Input has InputProps
rilay.form('test')
.add({
type: 'input', // ✅ Autocompletes from registry
props: { label: '' } // ✅ Typed as InputProps
})
.add({
type: 'unknown', // ❌ Compile error — not registered
});No any escape hatches — the registry is the single source of truth for types. Learn more
3. Universal validation (Standard Schema)
Any Standard Schema library works directly — zod, valibot, arktype — and mixes with the built-in helpers:
validation: { validate: [required(), z.string().min(8)] }Validation timing is form-level and mirrors React Hook Form: .setValidation({ mode, reValidateMode }) — mode defaults to 'onTouched' (validate on first blur, then live), reValidateMode to 'onChange'. Learn more
4. Declarative conditions (no useEffect)
.add({
id: 'company',
type: 'input',
conditions: {
visible: when('accountType').equals('business').build(),
required: when('plan').in(['pro', 'enterprise']).build(),
disabled: when('revenue').lessThan(10000).build(),
}
})Cross-step conditions work the same way in workflows — reference another step's field as when('stepId.fieldId'). Learn more
5. Production-ready workflow engine
Multi-step flows with navigation, persistence, analytics, and plugins:
import { LocalStorageAdapter } from 'rilaykit';
import { Flow } from 'rilaykit/react';
const onboarding = rilay.flow('onboarding', 'User Onboarding')
.step({ id: 'account', title: 'Account', formConfig: accountForm })
.step({ id: 'profile', title: 'Profile', formConfig: profileForm })
.configure({
persistence: { adapter: new LocalStorageAdapter({ keyPrefix: 'onboarding_' }) },
analytics: { onStepComplete: (id) => analytics.track('step_done', { step: id }) },
})
.build();
<Flow of={onboarding} onComplete={(data, meta) => save(data)}>
<Flow.Progress />
<Flow.Body />
<Flow.Back />
<Flow.Skip />
<Flow.Next />
</Flow>onComplete(data, meta) receives only answered visible steps (skipped or hidden steps are absent), plus meta.visitedSteps / skippedSteps / passedSteps. Persistence adapters (localStorage, custom), navigation guards, and a plugin system are built in. Learn more
6. AI-native (agent package)
Because forms and flows are data, an LLM can emit them. @rilaykit/agent (bundled in rilaykit) turns your catalog into a system-prompt manifest and schema-only UI tools, with adapters for the AI SDK and the Anthropic SDK:
import { manifest, uiTools } from 'rilaykit';
const catalog = rilay.use(uiTools()); // registers show_form / show_flow / show_component
const systemPrompt = manifest(catalog); // Markdown catalog for the modelThe model calls show_form, your app renders it, and the user's submission resolves back as the tool result. Learn more
Architecture
RilayKit generates zero HTML and zero CSS — it's pure logic. Bring shadcn/ui, Material UI, Mantine, or your own design system: register different renderers, the form config stays identical. One schema, N brands.
Install the all-in-one package:
pnpm add rilaykitBuilders and validators import from rilaykit; components and hooks from rilaykit/react. Granular packages (@rilaykit/core, @rilaykit/forms, @rilaykit/workflow, @rilaykit/agent) are also available — see Installation.
Get Started
Quick Start
Working form in 5 minutes. Best for hands-on learners.
Installation
Package installation and system requirements.
Your First Form
Step-by-step tutorial with detailed explanations.
Why RilayKit?
Deep dive into the schema-first approach and when to use it.