rilaykit

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.

npm version

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

CriteriaReact Hook FormRilayKit
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.

Read the detailed comparison


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 model

The 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 rilaykit

Builders 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


On this page