Kelex
MITZod Schema → React Form
Point it at a Zod schema. Get a form component. Introspects your schema at build time, maps fields to UI components, and generates a complete React form with TanStack Form. No configuration, no templates, no magic.
Pipeline
1. Quick Start
Requirements
- Node.js 18+
- Zod v4 (zod@^3.25 / "zod/v4" import)
- React 18+ (for generated output)
- TanStack Form (for generated output)
Install
pnpm add @ezmode-games/kelex Define a Schema
import { z } from "zod/v4";
export const contactSchema = z.object({
name: z.string().min(1).max(100),
email: z.email(),
message: z.string().min(10).max(500),
}); Generate
pnpm kelex ./src/schemas/contact.ts
Writes ContactForm.tsx to the same directory. Done.
2. CLI
pnpm kelex <schema-file> [options] Options
-o, --output <path> Output file path. Default: same directory as schema. -n, --name <name> Form component name. Default: inferred from schema export. -i, --ui-import <path> Import path for UI components. Default: @/components/ui 3. Supported Types
Scalar Types
z.string() → Input z.number() → Input type=number z.boolean() → Checkbox z.date() → Date picker z.email() → Input type=email z.url() → Input type=url Composite Types
z.enum([...]) → Select z.array() → Multi-select / Checkbox group z.object() → Nested fieldset Modifiers
.optional() Field not required .nullable() Allows null value .describe() Sets field label .min() .max() Validation constraints Constraints
All Zod validation chains are introspected and preserved. String length, number range, regex patterns, array min/max items. The generated form includes matching client-side validation.
4. Programmatic API
introspect()
Takes a Zod schema, returns a FormDescriptor describing every field,
its type, constraints, and metadata.
import { introspect } from "@ezmode-games/kelex";
const descriptor = introspect(contactSchema, {
schemaExportName: "contactSchema",
schemaImportPath: "./schemas/contact",
});
// descriptor.fields: FieldDescriptor[]
// Each field has: name, label, type, isOptional,
// isNullable, constraints, metadata generate()
Takes a FormDescriptor, generates a complete React form component.
import { generate } from "@ezmode-games/kelex";
const result = generate({
form: descriptor,
componentName: "ContactForm",
});
// result.code: string (full .tsx file contents) resolveField()
Maps a FieldDescriptor to a ComponentConfig.
Determines which UI component to render and with what props.
import { resolveField, defaultMappingRules } from "@ezmode-games/kelex";
const config = resolveField(field, defaultMappingRules());
// config.componentType: "input" | "select" | "checkbox" | ...
// config.props: Record<string, unknown> 5. Schema Writer
The reverse pipeline. Takes a FormDescriptor and generates a Zod v4 source file.
This closes the loop for visual schema builders that produce FormDescriptor as their
output format.
writeSchema()
import { writeSchema } from "@ezmode-games/kelex";
const result = writeSchema({
form: descriptor,
});
// result.code: string (valid Zod v4 source file)
// result.warnings: string[] Supported Types
String (including format types: email, url, uuid), number, boolean, date, enum, and arrays of scalars/enums.
Generated Output
import { z } from "zod/v4";
export const contactSchema = z.object({
name: z.string().min(1).max(100),
email: z.email(),
message: z.string().min(10).max(500),
});
export type Contact = z.infer<typeof contactSchema>; Round-Trip Fidelity
Schema → introspect → writeSchema → introspect again produces matching descriptors. Constraints, formats, optionality, and nullability are preserved through the round trip.
6. Full Example
Schema
import { z } from "zod/v4";
export const userProfileSchema = z.object({
username: z.string()
.min(3)
.max(20)
.describe("Username"),
email: z.email()
.describe("Email Address"),
age: z.number()
.int()
.min(13)
.max(120)
.optional()
.describe("Age"),
role: z.enum(["admin", "editor", "viewer"])
.describe("Role"),
bio: z.string()
.max(500)
.optional()
.nullable()
.describe("Bio"),
tags: z.array(z.string())
.min(1)
.max(5)
.describe("Tags"),
}); Generate
pnpm kelex ./src/schemas/user-profile.ts -o ./src/components/UserProfileForm.tsx Output
A complete, type-safe React form component with:
- Text inputs for username, email, bio
- Number input for age with min/max
- Select dropdown for role
- Multi-input for tags array
- Validation matching every Zod constraint
- Labels from .describe() strings
- Optional markers on age and bio
Stack
MIT Licensed. View source on GitHub.