• Stars
    star
    1,065
  • Rank 43,376 (Top 0.9 %)
  • Language
    TypeScript
  • Created over 1 year ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🌟 A React component that automatically creates a @shadcn/ui form based on a zod schema.

<AutoForm /> for @shadcn/ui

AutoForm is a React component that automatically creates a @shadcn/ui form based on a zod schema.

A live demo can be found at https://vantezzen.github.io/auto-form/.

AutoForm demo

Installation

The component depends on the following components from shadcn/ui:

  • accordion
  • button
  • calendar
  • card
  • checkbox
  • form
  • input
  • label
  • popover
  • radio-group
  • select
  • separator
  • switch
  • textarea
  • toggle

You can install them all at once with:

npx shadcn-ui@latest add accordion button calendar card checkbox form input label popover radio-group select separator switch textarea toggle

To install the component itself, copy auto-form.tsx and date-picker.tsx from src/components/ui to your project's ui folder.

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)

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

Usage

Basic 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",
        },
      }}
    >
      {/* 
      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>
  );
}

Next.js and RSC

AutoForm can only be used inside a client-side React component due to serialization of the zod schema and values to your event listeners. If you want to use it in a Next.js app, simply mark your component with "use client":

// MyPage.tsx
export default function MyPage() {
  return (
    <div>
      <MyForm />
    </div>
  );
}

// MyForm.tsx
"use client";
import AutoForm from "./components/ui/auto-form";
export default function MyForm() {
  return <AutoForm onSubmit={...} ... />;
}

Zod configuration

Validations

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

Autoform is able to automatically transform some of zod's validation elements into HTML attributes. For example, if you use zod.string().min(8), the input will automatically have a minlength="8" attribute.

Validation methods that are not supported by HTML will automatically be checked when the form is submitted.

Descriptions

You can use the describe method to set a label and description 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"
});

Coercion

When using numbers and dates, you should use coerce. This is because input elements may return a string that should automatically be converted.

const formSchema = z.object({
  favouriteNumber: z.coerce.number(),
  birthday: z.coerce.date(),
});

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),
});

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,
}
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.coerce.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.

Field configuration

As zod doesn't allow adding other properties to the schema, you can use the fieldConfig prop to add additional configuration for the UI of each field.

<AutoForm
  fieldConfig={{
    // Add config for each field here - don't add the field name to keep all defaults
    username: {
      // Configuration here
    },
  }}
/>

Input props

You can use the inputProps property to pass props to the input component. You can use any props that the HTML component accepts.

<AutoForm
  fieldConfig={{
    username: {
      inputProps: {
        type: "text",
        placeholder: "Username",
      },
    },
  }}
/>

// This will be rendered as:
<input type="text" placeholder="Username" /* ... */ />

Field type

By default, AutoForm will use the Zod type to determine which input component to use. You can override this by using the fieldType property.

<AutoForm
  fieldConfig={{
    sendMeMails: {
      // Booleans use a checkbox by default, use a switch instead
      fieldType: "switch",
    },
  }}
/>

The complete list of supported field types is typed. Current supported types are:

  • "checkbox" (default for booleans)
  • "switch"
  • "date" (default for dates)
  • "select" (default for enums)
  • "radio"
  • "textarea"
  • "fallback" (default for everything else, simple input field)

Alternatively, you can pass a React component to the fieldType property to use a custom component.

<AutoForm
  fieldConfig={{
    sendMeMails: {
      fieldType: ({
        label,
        isRequired,
        field,
        fieldConfigItem,
        fieldProps,
      }: AutoFormInputComponentProps) => (
        <FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
          <FormControl>
            <Switch
              checked={field.value}
              onCheckedChange={field.onChange}
              {...fieldProps}
            />
          </FormControl>
          <div className="space-y-1 leading-none">
            <FormLabel>
              {label}
              {isRequired && <span className="text-destructive"> *</span>}
            </FormLabel>
            {fieldConfigItem.description && (
              <FormDescription>{fieldConfigItem.description}</FormDescription>
            )}
          </div>
        </FormItem>
      ),
    },
  }}
/>

Description

You can use the description property to add a description below the field.

<AutoForm
  fieldConfig={{
    username: {
      description:
        "Enter a unique username. This will be shown to other users.",
    },
  }}
/>

You can use JSX in the description.

Custom parent component

You can use the renderParent property to customize the parent element of the input to add adornments etc. By default, this is a React fragment.

<AutoForm
  fieldConfig={{
    username: {
      renderParent: ({ children }) => (
        <div className="flex items-end gap-3">
          <div className="flex-1">
            {children} // This is the input with label etc.
          </div>
          <div>
            <Button type="button">Check</Button>
          </div>
        </div>
      ),
    },
  }}
/>

Accessing the form data

There are two ways to access the form data:

onSubmit

The preferred way is to use the onSubmit prop. This will be called when the form is submitted and the data is valid.

<AutoForm
  onSubmit={(data) => {
    // Do something with the data
    // Data is validated and coerced with zod automatically
  }}
/>

Controlled form

You can also use the values and onValuesChange props to control the form data yourself.

const [values, setValues] = useState<Partial<z.infer<typeof formSchema>>>({});

<AutoForm values={values} onValuesChange={setValues} />;

Please note that the data is not validated or coerced when using this method as they update immediately.

Alternatively, you can use onParsedValuesChange to get updated values only when the values can be validated and parsed with zod:

const [values, setValues] = useState<z.infer<typeof formSchema>>({});

<AutoForm values={values} onParsedValuesChange={setValues} />;

Submitting the form

You can use the AutoFormSubmit component to create a submit button.

<AutoForm>
  <AutoFormSubmit>Send now</AutoFormSubmit>
</AutoForm>
// or
<AutoForm>
  <button type="submit">Send now</button>
</AutoForm>

Adding other elements

All children passed to the AutoForm component will be rendered below the form.

<AutoForm>
  <AutoFormSubmit>Send now</AutoFormSubmit>
  <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>

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

  1. Fork the repository
  2. Clone your fork and install dependencies with npm install
  3. Run npm run dev to start the development server and make your changes
  4. Run npm run fix to run the formatter and linter
  5. Commit your changes and open a pull request

License

MIT

More Repositories

1

skip-silence

πŸ”‡ Browser extension to skip silent parts in videos and audio files on any webpage
TypeScript
354
star
2

blymp-io

πŸ“‚ Easily transfer files between devices
TypeScript
83
star
3

wrapped

πŸ“Š Personalized stats about your time on TikTok.
TypeScript
53
star
4

dontbugme

πŸ› DontBugMe allows you to easily use credentials from BugMeNot.com on any page.
TypeScript
49
star
5

minimalpedia

πŸ“– Alternative, minimalistic frontend for Wikipedia using React
CSS
29
star
6

react-electron-browser-view

:electron: A simple wrapper to make Electrons BrowserView compatible with React
JavaScript
28
star
7

cauldron-js

🍜 Cauldron.js - Minecraft Server in your browser
JavaScript
13
star
8

pinforgithub

πŸ“Œ Save repositories on GitHub for later without having to star them
JavaScript
10
star
9

Bangs-For-Google

Automatically use DuckDuckGo for !Bangs, keeping Google for all other searches
JavaScript
10
star
10

plasmo-state

♻️ Sync state across content script, background workers and the popup in Plasmo extensions
TypeScript
9
star
11

multithreaded-brainfuck-c

A Brainfuck interpreter in C that allows writing Brainfuck programs with multithreading support
C
7
star
12

ewmail

βœ‰οΈ Ew, Mail! is a web browser extension that simplifies the usage of temporary email addresses from Temp-Mail
JavaScript
7
star
13

nftlicense

β›“ Software licensing using NFTs
TypeScript
6
star
14

sprous

🌱 Add a support system/knowledge base to your website with sprous.
JavaScript
6
star
15

portfolio

πŸ–₯ Personal portfolio
Svelte
6
star
16

quill-languagetool

βœ’οΈ LanguageTool integration for Quill.js editors
TypeScript
6
star
17

vowserdb

πŸ—„ vowserDB allows you to use CSV or JSON files as a database in PHP
PHP
5
star
18

nordigen

πŸ’³ Unofficial JavaScript API Library for Nordigen
TypeScript
5
star
19

wonderrail

πŸš„ Discover the smartest way to Interrail
TypeScript
5
star
20

git-dev-time

⏱ CLI to estimate the time a developer spend on a repository
JavaScript
5
star
21

Google-Keep-to-Standardnotes-Converter

πŸ—’ Convert Google Keep Takeout archive into Standardnotes archive
HTML
5
star
22

lockbox

A secure, end-to-end encrypted secret sharing service
TypeScript
4
star
23

logo_generator

πŸ§ͺ Create Material Logos using Font Awesome icons
JavaScript
3
star
24

Did-you-mean-for-GitHub

❓ A browser extension that suggests you similar repository names when you get a 404 error on GitHub
CSS
3
star
25

journal

πŸ““ Minimal blogging CMS for blogs with static files
PHP
2
star
26

sn-theme-material

πŸ–₯ Dark Material Theme for StandardNotes
CSS
2
star
27

watermark

πŸ‘οΈ Add hidden watermarks to uncompressed images (PNG, BMP etc.) that do not rely on image metadata by tweaking RGB values
TypeScript
2
star
28

electric-squid

A wrapper and proxy to let flying-squid run in the browser.
TypeScript
2
star
29

plasmo-package-template

✨ Template for writing packages interacting with Plasmo
TypeScript
1
star
30

seed

🏡️ Laravel-inspired seeding system for JavaScript
TypeScript
1
star
31

gpt-chatbot

Basic scripts to train and use GPT-3 as a chatbot with custom data
Python
1
star
32

learn-rust

πŸ¦€ Testing ground for learning Rust
Rust
1
star
33

rust_json_parser

πŸ—œ Simple JSON parser in Rust
Rust
1
star
34

cors-audio-extension-test

🀺 Test to see if CORS can be unblocked by webextensions to allow "audioContext.createMediaElementSource"
JavaScript
1
star
35

lora-net

πŸ“‘ CLI and Web App for building an adhoc, multihop network using LoRa wireless modules
TypeScript
1
star
36

franz-recipe-standardnotes

πŸ—’ StandardNotes recipe for Franz
CSS
1
star