Get Started

Installation

Assuming you already have React and React Router v7 installed, you'll need the following packages:

npm install remix-forms composable-functions zod react-hook-form

Write your schema

Compose a zod schema that will be used in your action, mutation function, form generation, server-side validation, and client-side validation.

import { z } from 'zod'

const schema = z.object({
  firstName: z.string().min(1),
  email: z.string().min(1).email(),
})

Create your mutation

Create a mutation function wrapped by Composable Functions' applySchema. Remix Forms will parse the request's formData and send it to the mutation.

applySchema will ensure the mutation only performs if the arguments are valid.
If something goes bad, it will return a list of errors for us.

import { applySchema } from 'composable-functions'

const mutation = applySchema(schema)(async (values) => (
  console.log(values) /* or anything else, like saveMyValues(values) */
))

Create your action

If the mutation is successful, formAction will redirect to successPath. If not, it will return errors and values to pass to SchemaForm.

import { formAction } from 'remix-forms'

export const action = async ({ request }: Route.ActionArgs) =>
  formAction({
    request,
    schema,
    mutation,
    successPath: '/success', /* path to redirect on success */
  })

Create a basic form

If you don't want any custom UI in the form, you can render SchemaForm without children and it will generate all the inputs, labels, error messages and button for you.

import { SchemaForm } from 'remix-forms'

export default () => <SchemaForm schema={schema} />

Custom form, standard components

If you want a custom UI for your form, but don't need to customize the rendering of fields, errors, and buttons, do it like this:

<SchemaForm schema={schema}>
  {({ Field, Errors, Button }) => (
    <>
      <Field name="firstName" label="First name" />
      <Field name="email" label="E-mail" />
      <em>You'll hear from us at this address ๐Ÿ‘†๐Ÿฝ</em>
      <Errors />
      <Button />
    </>
  )}
</SchemaForm>

Custom field, standard components

If you want a custom UI for a specific field, but don't need to customize the rendering of the label, input/select, and errors, do this:

<SchemaForm schema={schema}>
  {({ Field, Errors, Button }) => (
    <>
      <Field name="firstName" label="First name" />
      <Field name="email">
        {({ Label, SmartInput, Errors }) => (
          <>
            <Label>E-mail</Label>
            <em>You'll hear from us at this address ๐Ÿ‘‡๐Ÿฝ</em>
            <SmartInput />
            <Errors />
          </>
        )}
      </Field>
      <Errors />
      <Button />
    </>
  )}
</SchemaForm>

100% custom input

If you want a 100% custom input, you can access React Hook Form's register (and everything else) through the SchemaForm's children and go nuts:

<SchemaForm schema={schema}>
  {({ Field, Errors, Button, register }) => (
    <>
      <Field name="firstName" label="First name" />
      <Field name="email" label="E-mail">
        {({ Label, Errors }) => (
          <>
            <Label />
            <input {...register('email')} />
            <Errors />
          </>
        )}
      </Field>
      <Errors />
      <Button />
    </>
  )}
</SchemaForm>

[Optional] Customize styles

Remix Forms doesn't ship any styles, so you might want to configure basic styles for your forms. Let's edit our custom SchemaForm component:

import type { SchemaFormProps, FormSchema } from 'remix-forms'
import { SchemaForm as BaseForm } from 'remix-forms'

function SchemaForm<Schema extends FormSchema>(props: SchemaFormProps<Schema>) {
  return (
    <BaseForm<Schema>
      className={/* your form classes */}
      fieldComponent={/* your custom Field */}
      labelComponent={/* your custom Label */}
      inputComponent={/* your custom Input */}
      multilineComponent={/* your custom Multiline */}
      selectComponent={/* your custom Select */}
      checkboxComponent={/* your custom Checkbox */}
      checkboxWrapperComponent={/* your custom checkbox wrapper */}
      buttonComponent={/* your custom Button */}
      fieldErrorsComponent={/* your custom FieldErrors */}
      globalErrorsComponent={/* your custom GlobalErrors */}
      errorComponent={/* your custom Error */}
      {...props}
    />
  )
}

export { SchemaForm }

Check out how we customized the styles for this website. We basically created a bunch of UI components and passed them to our custom form.

PS: you don't need to customize everything. We'll use standard html tags if you don't.

That's it!

Now go play ๐Ÿ˜Š

Please create issues as you encounter them. We appreciate the contribution!

Appreciation

Remix Forms is a thin layer on top of giants. It wouldn't be possible without TypeScript, React Hook Form, Remix, React Router, Zod, Composable Functions, and a multitude of other open-source projects. Thank you!