Redpanda UI
RC
Redpanda UI

Auto Form

Automatically generate a form from Zod schema.

Made by vantezzen

What is AutoForm

AutoForm is a drop-in form builder for your internal and low-priority forms with existing zod schemas. For example, if you already have zod schemas for your API and want to create a simple admin panel to edit user profiles, simply pass the schema to AutoForm and you're done.

Loading component...

Installation

Field types

Currently, these field types are supported out of the box:

  • boolean (checkbox, switch)
  • date (date picker)
  • enum (select, radio group)
  • number (input)
  • string (input, textfield)
  • file (file)

You can add support for other field types by adding them to the INPUT_COMPONENTS object in components/redpanda-ui/auto-form.tsx.

Zod configuration

Validations

Your form schema can use any of zod's validation methods including refine.

When to use

Use AutoForm when you need to quickly generate forms from existing Zod schemas without manual form building:

Use AutoForm when:

  • Building admin panels or internal tools
  • Rapid prototyping forms
  • You already have Zod schemas for your API
  • Creating simple CRUD interfaces
  • Working with straightforward field types (string, number, boolean, enum, date)

Don't use AutoForm when:

  • Building public-facing forms with specific UX requirements
  • Need complex custom layouts or styling
  • Working with unsupported field types
  • Performance is critical (large schemas)
  • Need fine-grained control over form behavior

Anatomy

AutoForm automatically generates form structure from Zod schema with these components:

AutoForm Container
┌─────────────────────────────────────────────────────────────┐
│ Form Provider (react-hook-form)                             │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Generated Form Fields                                   │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ AutoFormField (string)                              │ │ │
│ │ │ ├── Label                                           │ │ │
│ │ │ ├── Input                                           │ │ │
│ │ │ ├── Description (optional)                          │ │ │
│ │ │ └── Error Message                                   │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ AutoFormField (object)                              │ │ │
│ │ │ └── Accordion with nested fields                    │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ AutoFormField (array)                               │ │ │
│ │ │ └── Array wrapper with add/remove buttons           │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Submit Button (AutoFormSubmit)                          │ │
│ └─────────────────────────────────────────────────────────┘ │
│ [Custom children content]                                   │
└─────────────────────────────────────────────────────────────┘

Field Type Mapping:
- z.string() → Input/Textarea
- z.number() → Input[type="number"]
- z.boolean() → Checkbox/Switch
- z.date() → DatePicker
- z.enum() → Select
- z.object() → Accordion section
- z.array() → Array field wrapper

Component Hierarchy:

  1. AutoForm (Root): Main form container with react-hook-form provider
  2. AutoFormField: Individual field wrapper that determines field type
  3. Field Components: Generated based on Zod schema type
  4. AutoFormSubmit: Optional submit button component

Key Features:

  • Schema-driven: Automatically infers field types from Zod schema
  • Validation: Uses Zod validation rules for form validation
  • Dependencies: Supports conditional field visibility
  • Customization: fieldConfig allows per-field customization
  • Nested Objects: Creates accordion sections for object schemas
  • Arrays: Supports dynamic array fields with add/remove

Usage

"use client";
import AutoForm, { AutoFormSubmit } from "./components/ui/auto-form";
import * as z from "zod";

// Define your form schema using zod
const formSchema = z.object({
  username: z
    .string({
      required_error: "Username is required.",
    })
    // You can use zod's built-in validation as normal
    .min(2, {
      message: "Username must be at least 2 characters.",
    }),

  password: z
    .string({
      required_error: "Password is required.",
    })
    // Use the "describe" method to set the label
    // If no label is set, the field name will be used
    // and un-camel-cased
    .describe("Your secure password")
    .min(8, {
      message: "Password must be at least 8 characters.",
    }),

  favouriteNumber: z.coerce // When using numbers and dates, you must use coerce
    .number({
      invalid_type_error: "Favourite number must be a number.",
    })
    .min(1, {
      message: "Favourite number must be at least 1.",
    })
    .max(10, {
      message: "Favourite number must be at most 10.",
    })
    .default(5) // You can set a default value
    .optional(),

  acceptTerms: z
    .boolean()
    .describe("Accept terms and conditions.")
    .refine((value) => value, {
      message: "You must accept the terms and conditions.",
      path: ["acceptTerms"],
    }),

  // Date will show a date picker
  birthday: z.coerce.date().optional(),

  sendMeMails: z.boolean().optional(),

  // Enum will show a select
  color: z.enum(["red", "green", "blue"]),

  // Create sub-objects to create accordion sections
  address: z.object({
    street: z.string(),
    city: z.string(),
    zip: z.string(),
  }),
});

function App() {
  return (
    <AutoForm
      // Pass the schema to the form
      formSchema={formSchema}
      // You can add additional config for each field
      // to customize the UI
      fieldConfig={{
        password: {
          // Use "inputProps" to pass props to the input component
          // You can use any props that the component accepts
          inputProps: {
            type: "password",
            placeholder: "••••••••",
          },
        },
        favouriteNumber: {
          // Set a "description" that will be shown below the field
          description: "Your favourite number between 1 and 10.",
        },
        acceptTerms: {
          inputProps: {
            required: true,
          },
          // You can use JSX in the description
          description: (
            <>
              I agree to the{" "}
              <a
                href="#"
                className="text-primary underline"
                onClick={(e) => {
                  e.preventDefault();
                  alert("Terms and conditions clicked.");
                }}
              >
                terms and conditions
              </a>
              .
            </>
          ),
        },

        birthday: {
          description: "We need your birthday to send you a gift.",
        },

        sendMeMails: {
          // Booleans use a checkbox by default, you can use a switch instead
          fieldType: "switch",
        },
      }}
      // Optionally, define dependencies between fields
      dependencies={[
        {
          // Hide "color" when "sendMeMails" is not checked as we only need to
          // know the color when we send mails
          sourceField: "sendMeMails",
          type: DependencyType.HIDES,
          targetField: "color",
          when: (sendMeMails) => !sendMeMails,
        },
      ]}
    >
      {/* 
      Pass in a AutoFormSubmit or a button with type="submit".
      Alternatively, you can not pass a submit button
      to create auto-saving forms etc.
      */}
      <AutoFormSubmit>Send now</AutoFormSubmit>

      {/*
      All children passed to the form will be rendered below the form.
      */}
      <p className="text-gray-500 text-sm">
        By submitting this form, you agree to our{" "}
        <a href="#" className="text-primary underline">
          terms and conditions
        </a>
        .
      </p>
    </AutoForm>
  );
}

Validations

Your form schema can use any of zod's validation methods including refine.

Descriptions

You can use the describe method to set a label for each field. If no label is set, the field name will be used and un-camel-cased.

const formSchema = z.object({
  username: z.string().describe('Your username'),
  someValue: z.string(), // Will be "Some Value"
})

You can also configure the label with fieldConfig too.

Optional fields

By default, all fields are required. You can make a field optional by using the optional method.

const formSchema = z.object({
  username: z.string().optional(),
})

Default values

You can set a default value for a field using the default method.

const formSchema = z.object({
  favouriteNumber: z.number().default(5),
})

If you want to set default value of date, convert it to Date first using new Date(val).

Sub-objects

You can nest objects to create accordion sections.

const formSchema = z.object({
  address: z.object({
    street: z.string(),
    city: z.string(),
    zip: z.string(),

    // You can nest objects as deep as you want
    nested: z.object({
      foo: z.string(),
      bar: z.string(),

      nested: z.object({
        foo: z.string(),
        bar: z.string(),
      }),
    }),
  }),
})

Like with normal objects, you can use the describe method to set a label and description for the section:

const formSchema = z.object({
  address: z
    .object({
      street: z.string(),
      city: z.string(),
      zip: z.string(),
    })
    .describe('Your address'),
})

Select/Enums

AutoForm supports enum and nativeEnum to create select fields.

const formSchema = z.object({
  color: z.enum(['red', 'green', 'blue']),
})

enum BreadTypes {
  // For native enums, you can alternatively define a backed enum to set a custom label
  White = 'White bread',
  Brown = 'Brown bread',
  Wholegrain = 'Wholegrain bread',
  Other,
}
// Keep in mind that zod will validate and return the enum labels, not the enum values!
const formSchema = z.object({
  bread: z.nativeEnum(BreadTypes),
})

Arrays

AutoForm supports arrays of objects. Because inferring things like field labels from arrays of strings/numbers/etc. is difficult, only objects are supported.

const formSchema = z.object({
  guestListName: z.string(),
  invitedGuests: z
    .array(
      // Define the fields for each item
      z.object({
        name: z.string(),
        age: z.number(),
      })
    )
    // Optionally set a custom label - otherwise this will be inferred from the field name
    .describe('Guests invited to the party'),
})

Arrays are not supported as the root element of the form schema.

You also can set default value of an array using .default(), but please make sure the array element has same structure with the schema.

const formSchema = z.object({
  guestListName: z.string(),
  invitedGuests: z
    .array(
      // Define the fields for each item
      z.object({
        name: z.string(),
        age: z.number(),
      })
    )
    .describe('Guests invited to the party')
    .default([
      { name: 'John', age: 24, },
      { name: 'Jane', age: 20, },
    ]),
})

Credits

  • We take our inspiration from Shadcn UI for the form component and style.

Built by malinskibeniamin. The source code is available on GitHub.

On this page