Quick Start
Get up and running with RilayKit in 5 minutes. Perfect for developers who want to jump straight into code.
Quick Start
A working, validated form in 5 minutes.
1. Install
pnpm add rilaykitnpm install rilaykityarn add rilaykit2. Create a Component
A renderer receives a ComponentRenderContext: your props, plus a field binding (value, onChange, onBlur, error, touched).
import type { ComponentRenderContext } from 'rilaykit';
interface InputProps {
label: string;
type?: string;
placeholder?: string;
}
export function Input({ id, props, field }: ComponentRenderContext<InputProps>) {
return (
<div className="mb-4">
<label htmlFor={id} className="block text-sm font-medium mb-1">
{props.label}
</label>
<input
id={id}
type={props.type ?? 'text'}
value={(field?.value as string) ?? ''}
onChange={(e) => field?.onChange(e.target.value)}
onBlur={field?.onBlur}
placeholder={props.placeholder}
className="w-full p-2 border rounded"
/>
{field?.error && (
<p role="alert" className="text-red-500 text-sm mt-1">
{field.error[0].message}
</p>
)}
</div>
);
}3. Register Components
import { ril } from 'rilaykit';
import { Input } from '../components/Input';
export const rilay = ril.create().component('input', { renderer: Input });4. Build the Form
import { email, required } from 'rilaykit';
import { rilay } from '../lib/rilay';
export const loginForm = rilay.form('login')
.add({
id: 'email',
type: 'input',
props: { label: 'Email', type: 'email' },
validation: { validate: [required(), email()] },
})
.add({
id: 'password',
type: 'input',
props: { label: 'Password', type: 'password' },
validation: { validate: [required()] },
})
.build();By default fields validate on first blur, then re-validate on every keystroke. Tune with .setValidation({ mode, reValidateMode }).
5. Render It
Components and hooks import from rilaykit/react:
import { Form, FormField } from 'rilaykit/react';
import { loginForm } from '../forms/login';
export function LoginForm() {
return (
<Form of={loginForm} onSubmit={(data) => console.log('Login:', data)}>
<FormField id="email" />
<FormField id="password" />
<button type="submit">Sign In</button>
</Form>
);
}Done — mount <LoginForm /> anywhere.
Common Patterns
Multiple Input Types
export const rilay = ril.create()
.component('input', { renderer: Input })
.component('textarea', { renderer: TextareaInput })
.component('select', { renderer: SelectInput });Validation with Zod
Any Standard Schema library works directly — no adapter:
import { z } from 'zod';
const userForm = rilay.form('user')
.add({
id: 'email',
type: 'input',
validation: { validate: z.string().email() },
});Conditional Fields
import { when } from 'rilaykit';
const accountForm = rilay.form('account')
.add({
id: 'accountType',
type: 'select',
props: { options: [{ value: 'business', label: 'Business' }] },
})
.add({
id: 'companyName',
type: 'input',
conditions: { visible: when('accountType').equals('business') },
});Cross-Field Validation
Form-level rules run on the same cadence as field validation. An issue whose path names a field attaches to that field; pathless issues land in the __form__ bucket, read via useFormErrors():
import { z } from 'zod';
const signupForm = rilay.form('signup')
.add({ id: 'password', type: 'input', props: { label: 'Password', type: 'password' } })
.add({ id: 'confirmPassword', type: 'input', props: { label: 'Confirm', type: 'password' } })
.setValidation({
validate: z
.object({ password: z.string(), confirmPassword: z.string() })
.refine((d) => d.password === d.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
}),
})
.build();What's Next?
- Your First Form — step-by-step tutorial
- Validation — timing modes, Standard Schema, async
- Advanced Forms — conditions, effects, repeatables
- Workflows — multi-step flows and wizards
- Examples — real-world patterns
- Discord — get help from the community