Form

Create fully functional React forms with just a few lines of code.

The Form component is an abstraction around React Hook Form and follows the WAI specifications for forms.

Import#

  • Form: The wrapper component provides context, state, and focus management.
  • FormLayout: Create consistent field spacing and positioning.
  • Field: Renders a fully functional form control, supports multiple types. Must be a child of Form.
  • DisplayIf: Conditionally render parts of a form.
  • SubmitButton: A button with type submit and default color scheme primary and isLoading state when the form is submitting.
import {
Form,
FormLayout,
Field,
DisplayIf,
SubmitButton,
} from '@saas-ui/react'

Best practises#

Do
  • Keep optional fields to a minimum.
  • Make it clear which fields are required or optional.
  • Group related information in sections to make forms easier to scan.
  • Consider splitting up long forms into multiple steps.
  • Position the submit button consistenly throughout all forms.
Don't
  • Use too many ungrouped fields on a single page.
  • Use tabs inside forms.

Usage#

Basic form#

function BasicForm() {
const onSubmit = (params) => {
console.log(params)
return new Promise((resolve) => {
setTimeout(resolve, 1000)
})
}
return (
<Form
defaultValues={{
name: 'Saas UI',
description: '',
}}
onSubmit={onSubmit}
>
<FormLayout>
<Field
name="name"
label="Name"
type="text"
help="Choose a name for this project"
rules={{ required: true }}
/>
<Field
name="description"
type="textarea"
label="Description"
placeholder="Optional description"
/>
<SubmitButton>Create Project</SubmitButton>
</FormLayout>
</Form>
)
}

Schema resolvers#

Form supports all React Hook Form resolvers out of the box.

function CreateProject() {
const schema = Yup.object().shape({
name: Yup.string().required().label('Name'),
description: Yup.string().label('Description').min(50),
})
const onSubmit = (params) => {
console.log(params)
return new Promise((resolve) => {
setTimeout(resolve, 1000)
})
}
return (
<Form
defaultValues={{
name: '',
description: '',
}}
resolver={yupResolver(schema)}
onSubmit={onSubmit}
>
<FormLayout>
<Field
name="name"
label="Name"
type="text"
help="Choose a title for this project."
/>
<Field
name="description"
type="textarea"
label="Description"
help="Minimum 50 characters."
/>
<SubmitButton>Create Project</SubmitButton>
</FormLayout>
</Form>
)
}

Disable the submit button when untouched#

<Form>
<FormLayout>
<Field
name="title"
label="Title"
rules={{ required: 'Title is required' }}
/>
<Field name="description" type="textarea" label="Description" />
<SubmitButton disableIfUntouched />
</FormLayout>
</Form>

Disable the submit button when invalid#

<Form onSubmit={saveHandler}>
<FormLayout>
<Field
name="email"
label="Email"
rules={{ required: true, type: 'email' }}
/>
<Field
name="terms"
type="checkbox"
label="I accept the terms & conditions."
rules={{ required: true }}
/>
<SubmitButton disableIfInvalid />
</FormLayout>
</Form>

Use your own submit button#

<Form onSubmit={saveHandler}>
<FormLayout>
<Field name="title" label="Title" />
<Button type="submit" colorScheme="teal">
Submit
</Button>
</FormLayout>
</Form>
<Form onSubmit={saveHandler}>
<FormLayout>
<Heading size="md">Personal information</Heading>
<FormLayout columns="2">
<Field name="firstname" label="Name" />
<Field name="lastname" label="Last name" />
</FormLayout>
<Field name="email" label="Email address" />
<Heading size="md" mt="4">
Address
</Heading>
<FormLayout>
<Field name="address" label="Address" />
<Field name="city" label="City" />
</FormLayout>
<Heading size="md" mt="4">
Billing information
</Heading>
<FormLayout columns="2">
<Field name="card" label="Card number" />
<FormLayout columns="2">
<Field name="exp" label="Expiration date" />
<Field name="cvc" label="CVC" />
</FormLayout>
</FormLayout>
<SubmitButton>Complete order</SubmitButton>
</FormLayout>
</Form>

Conditionally show fields#

<Form onSubmit={saveHandler}>
<FormLayout>
<Heading size="md">Personal information</Heading>
<FormLayout columns="2">
<Field name="firstname" label="Name" />
<Field name="lastname" label="Last name" />
</FormLayout>
<Field name="email" label="Email address" />
<Field
name="ship"
type="checkbox"
value={true}
label="Ship to my home address"
/>
<DisplayIf name="ship" condition={(ship) => !!ship}>
<FormLayout>
<Heading size="md" mt="4">
Address
</Heading>
<FormLayout>
<Field name="address" label="Address" />
<Field name="city" label="City" />
</FormLayout>
<Heading size="md" mt="4">
Billing information
</Heading>
<FormLayout columns="2">
<Field name="card" label="Card number" />
<FormLayout columns="2">
<Field name="exp" label="Expiration date" />
<Field name="cvc" label="CVC" />
</FormLayout>
</FormLayout>
</FormLayout>
</DisplayIf>
<SubmitButton>Complete order</SubmitButton>
</FormLayout>
</Form>

Access the form context#

You can get access to the form context using a render prop, the useFormContext hook or the form ref.

Render prop#

<Form onSubmit={saveHandler}>
{(form) => (
<FormLayout>
<Field name="title" label="Title" />
<SubmitButton>Submit</SubmitButton>
</FormLayout>
)}
</Form>

useFormContext#

function ResetButton() {
const form = useFormContext()
return <Button onClick={() => form.reset()}>Reset</Button>
}

Form ref#

function MyForm() {
const formRef = useRef(null)
return (
<Form ref={formRef} onSubmit={saveHandler}>
<FormLayout>
<Field name="title" label="Title" />
<SubmitButton>Submit</SubmitButton>
<Button onClick={() => formRef.current.reset()}>Reset</Button>
</FormLayout>
</Form>
)
}

Configure a default resolver#

In case you are using schemas in all your forms it's possible to configure a default resolver, this way you can simply pass schemas directly to your forms and save a few lines of boilerplate code.

// Add this somewhere in the root of your project.
import { Form } from '@saas-ui/react'
import { yupResolver, yupFieldResolver } from '@saas-ui/forms/yup'
import { AnyObjectSchema } from 'yup'
Form.getResolver = (schema: AnyObjectSchema) => yupResolver(schema) // @hookform/resolvers
Form.getFieldResolver = (schema: AnyObjectSchema) => yupFieldResolver(schema) // AutoForm field resolver
// You no longer need to set a resolver on each form, so you can do
<Form schema={schema} />
// instead of
<Form resolver={yupResolver(schema)} />

Typescript support#

type PostInputs = {
firstName: string
lastName: string
}
export const TypescriptForm = () => {
return (
<Form<PostInputs>
defaultValues={{
firstName: 'Eelco',
lastName: 'Wiersma',
}}
onSubmit={onSubmit}
>
<FormLayout>
<Field<PostInputs> name="firstName" label="First name" />
<Field<PostInputs> name="lastName" label="Last name" />
</FormLayout>
</Form>
)
}

Accessibility#

The Form component wraps the children in a HTML <form> element.

Keyboard Interaction#

KeyAction
EnterSubmit the form

Props#

onSubmitrequired

Description

The submit handler.

Type
SubmitHandler<TFieldValues>

children

Description

The form children, can be a render prop or a ReactNode.

Type
MaybeRenderProp<UseFormReturn<TFieldValues, any>>

context

Type
any

criteriaMode

Type
CriteriaMode

defaultValues

Type
{ [x: string]: any; }

delayError

Type
number

formRef

Description

Ref on the HTMLFormElement.

Type
RefObject<HTMLFormElement>

mode

Type
keyof ValidationMode

onChange

Description

Triggers when any of the field change.

Type
WatchObserver<TFieldValues>

onError

Description

Triggers when there are validation errors.

Type
SubmitErrorHandler<TFieldValues>

ref

Type
ForwardedRef<UseFormReturn<TFieldValues, any>>

resolver

Type
Resolver<TFieldValues, any>

reValidateMode

Type
"onBlur" | "onChange" | "onSubmit"

schema

Description

The form schema, currently supports Yup schema only.

Type
any

shouldFocusError

Type
boolean

shouldUnregister

Type
boolean

shouldUseNativeValidation

Type
boolean

Was this helpful?