Check out our talk at Remix Conf!

Get Started

Dependencies

Make sure you have Remix, Zod, React Hook Form, and Domain Functions in your project before using Remix Forms.

Installation

Assuming you already have react and remix installed, you'll need the following packages:

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

Basic styles

Remix Forms doesn't ship any styles, so you need to configure basic styles for your forms. Let's create a custom Form component for your project:

import { Form as RemixForm, FormProps } from 'remix-forms'
import { SomeZodObject } from 'zod'

export default function Form<Schema extends SomeZodObject>(
  props: FormProps<Schema>,
) {
  return (
    <RemixForm<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}
    />
  )
}

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.

With your custom Form in place, now you can use it instead of Remix Forms' for all your forms.

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

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().nonempty(),
  email: z.string().nonempty().email(),
})

Create your mutation

Create a mutation function using Domain Functions' makeDomainFunction. It's a function that receives the values from the form and performs the necessary mutations, such as storing data on a database.

Domain Functions will parse the request's formData and perform the mutation only if everything is valid. If something goes bad, it will return structured error messages for us.

import { makeDomainFunction } from 'domain-functions'

const mutation = makeDomainFunction(schema)(async (values) => (
  await saveMyValues(values) /* or anything else */
))

Create your action

If the mutation is successful, Remix Forms' formAction will redirect to successPath. If not, it will return errors and values to pass to Form.

import { formAction } from 'remix-forms'

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

Create a basic form

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

import { Form } from /* path to your custom Form */

export default () => <Form 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:

<Form 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 />
    </>
  )}
</Form>

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:

<Form 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 />
    </>
  )}
</Form>

100% custom input

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

<Form 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 />
    </>
  )}
</Form>

That's it!

Now go play! Keep in mind that we're just getting started and the APIs are unstable, so we appreciate your patience as we figure things out.

Also, please join Remix's community on Discord. We'll be there to provide you support on Remix Forms.

Appreciation

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