Kelex

MIT

Zod 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.

pnpm add @ezmode-games/kelex
Source npm

Pipeline

Zod Schema introspect() FormDescriptor
generate() → .tsx writeSchema() → .ts

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

New

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

TypeScript React Zod v4 TanStack Form

MIT Licensed. View source on GitHub.