Check out our talk at Remix Conf!

Get Started

Remix or React Router 6.4?

You can use Remix Forms with Remix, React Router 6.4, or even your custom framework. As long as you pass it a Form component and a couple of functions and hooks, it will work anywhere React runs.

Installation

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

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

Create your formAction function

First, let's create a formAction function. This will be used in your actions.

Somewhere within your app/, create a file named form-action.server.ts.

/app/form-action.server.ts

Important: do not forget to include the suffix .server in the file name otherwise server-side code will leak to the browser, triggering a confusing error.

import { createFormAction } from 'remix-forms'
// For Remix, import it like this
import { redirect, json } from '@remix-run/node'
// For React Router 6.4, like this
import { redirect, json } from 'react-router-dom'

const formAction = createFormAction({ redirect, json })

export { formAction }

Create your Form component

Next, let's create your project's custom Form component:

Alongside your form-action.server.ts, create a form.ts file and include the following code:

/app/form.ts
import { createForm } from 'remix-forms'
// For Remix, import it like this
import { Form as FrameworkForm, useActionData, useSubmit, useTransition as useNavigation } from '@remix-run/react'
// For React Router 6.4, like this
import { Form as FrameworkForm, useActionData, useSubmit, useNavigation } from 'react-router-dom'

const Form = createForm({ component: FrameworkForm, useNavigation, useSubmit, useActionData })

export { 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 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) => (
  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 Form.

import { formAction } from '~/form-action.server' /* path to your custom formAction */

export const action: ActionFunction = async ({ request }) =>
  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 Form without children and it will generate all the inputs, labels, error messages and button for you.

import { Form } from '~/form' /* 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>

[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 Form component:

import type { FormProps, FormSchema } from 'remix-forms'
import { createForm } from 'remix-forms'
// For Remix, import it like this
import { Form as FrameworkForm, useActionData, useSubmit, useTransition as useNavigation } from '@remix-run/react'
// For React Router 6.4, like this
import { Form as FrameworkForm, useActionData, useSubmit, useNavigation } from 'react-router-dom'

const RemixForm = createForm({ component: FrameworkForm, useNavigation, useSubmit, useActionData })

function Form<Schema extends FormSchema>(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}
    />
  )
}

export { Form }

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! 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 Seasoned's OSS 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, React Router, Zod, Domain Functions, and a multitude of other open-source projects. Thank you!