Form
This is experimental and may introduce breaking changes or be removed altogether in patch and minor versions without notice. Learn more in Experimental features.
Form is an accessible component with a collection of other components, such as FormLabel and FormInput.
#Installation
npm install reakit
Learn more in Get started.
#Usage
import {
unstable_useFormState as useFormState,
unstable_Form as Form,
unstable_FormLabel as FormLabel,
unstable_FormInput as FormInput,
unstable_FormMessage as FormMessage,
unstable_FormSubmitButton as FormSubmitButton,
} from "reakit/Form";
function Example() {
const form = useFormState({
values: { name: "" },
onValidate: (values) => {
if (!values.name) {
const errors = {
name: "How can we be friends without knowing your name?",
};
throw errors;
}
},
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<Form {...form}>
<FormLabel {...form} name="name">
Name
</FormLabel>
<FormInput {...form} name="name" placeholder="John Doe" />
<FormMessage {...form} name="name" />
<FormSubmitButton {...form}>Submit</FormSubmitButton>
</Form>
);
}
#Textareas
If your form requires a textarea instead of an input field, you can use the as prop on the FormInput component.
import {
unstable_useFormState as useFormState,
unstable_Form as Form,
unstable_FormLabel as FormLabel,
unstable_FormInput as FormInput,
unstable_FormMessage as FormMessage,
unstable_FormSubmitButton as FormSubmitButton,
} from "reakit/Form";
function Example() {
const form = useFormState({
values: { message: "" },
onValidate: (values) => {
if (!values.message) {
const errors = {
message: "Please enter a message.",
};
throw errors;
}
},
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<Form {...form}>
<FormLabel {...form} name="message">
Message
</FormLabel>
<FormInput
{...form}
name="message"
placeholder="What's on your mind?"
as="textarea"
/>
<FormMessage {...form} name="message" />
<FormSubmitButton {...form}>Submit</FormSubmitButton>
</Form>
);
}
#Arrays
Form supports array values seamlessly. For convenience, you can reliably use the array indexes as keys on the array fragments.
Focus is managed so adding a new item will move focus to the new input or to the first input if multiple inputs have been added.
import React from "react";
import {
unstable_useFormState as useFormState,
unstable_Form as Form,
unstable_FormLabel as FormLabel,
unstable_FormRemoveButton as FormRemoveButton,
unstable_FormPushButton as FormPushButton,
unstable_FormSubmitButton as FormSubmitButton,
unstable_FormInput as FormInput,
unstable_FormMessage as FormMessage,
} from "reakit/Form";
function Example() {
const form = useFormState({
values: {
people: [{ name: "", email: "" }],
},
onValidate: (values) => {
const errors = {};
values.people.forEach((value, i) => {
if (!value.email) {
if (!errors.people) {
errors.people = [];
}
if (!errors.people[i]) {
errors.people[i] = {};
}
errors.people[i].email =
"We can't sell data without an email, can we?";
}
});
if (Object.keys(errors).length) {
throw errors;
}
},
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<Form {...form}>
{form.values.people.map((_, i) => (
<React.Fragment key={i}>
<FormLabel {...form} name={["people", i, "name"]}>
Name
</FormLabel>
<FormInput {...form} name={["people", i, "name"]} />
<FormMessage {...form} name={["people", i, "name"]} />
<FormLabel {...form} name={["people", i, "email"]}>
Email
</FormLabel>
<FormInput {...form} type="email" name={["people", i, "email"]} />
<FormMessage {...form} name={["people", i, "email"]} />
<FormRemoveButton {...form} name="people" index={i}>
Remove person
</FormRemoveButton>
</React.Fragment>
))}
<br />
<br />
<FormPushButton {...form} name="people" value={{ name: "", email: "" }}>
Add person
</FormPushButton>
<FormSubmitButton {...form}>Submit</FormSubmitButton>
</Form>
);
}
#Checkbox
With FormCheckbox, you can either manage boolean values (single checkbox) or array values (checkbox group). Error messages can also be displayed.
import {
unstable_useFormState as useFormState,
unstable_Form as Form,
unstable_FormLabel as FormLabel,
unstable_FormCheckbox as FormCheckbox,
unstable_FormGroup as FormGroup,
unstable_FormSubmitButton as FormSubmitButton,
unstable_FormMessage as FormMessage,
} from "reakit/Form";
function Example() {
const form = useFormState({
values: {
accepted: false,
preferences: [],
},
onValidate: (values) => {
const errors = {};
if (!values.accepted) {
errors.accepted = "You must accept our not-so-evil conditions!";
}
if (!values.preferences.includes("JS")) {
errors.preferences = "Why not JS? It's so cool! 🙁";
}
if (Object.keys(errors).length) {
throw errors;
}
},
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<Form {...form}>
<FormCheckbox {...form} name="accepted" />
<FormLabel {...form} name="accepted">
Accept conditions
</FormLabel>
<FormMessage {...form} name="accepted" />
<FormGroup {...form} name="preferences">
<FormLabel {...form} as="legend" name="preferences">
Preferences
</FormLabel>
<label>
<FormCheckbox {...form} name="preferences" value="html" /> HTML
</label>
<label>
<FormCheckbox {...form} name="preferences" value="css" /> CSS
</label>
<label>
<FormCheckbox {...form} name="preferences" value="JS" /> JS
</label>
</FormGroup>
<FormMessage {...form} name="preferences" />
<FormSubmitButton {...form}>Submit</FormSubmitButton>
</Form>
);
}
#Radio
You can use FormRadio and FormRadioGroup to manage radio buttons. Error messages can also be displayed.
import {
unstable_useFormState as useFormState,
unstable_Form as Form,
unstable_FormLabel as FormLabel,
unstable_FormRadioGroup as FormRadioGroup,
unstable_FormRadio as FormRadio,
unstable_FormSubmitButton as FormSubmitButton,
unstable_FormMessage as FormMessage,
} from "reakit/Form";
function Example() {
const form = useFormState({
values: { choice: "" },
onValidate: (values) => {
if (values.choice !== "js") {
const errors = { choice: "YOU WILL BE FIRED!" };
throw errors;
}
},
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<Form {...form}>
<FormRadioGroup {...form} name="choice">
<FormLabel {...form} as="legend" name="choice">
Choice
</FormLabel>
<label>
<FormRadio {...form} name="choice" value="html" /> HTML
</label>
<label>
<FormRadio {...form} name="choice" value="css" /> CSS
</label>
<label>
<FormRadio {...form} name="choice" value="js" /> JS
</label>
</FormRadioGroup>
<FormMessage {...form} name="choice" />
<FormSubmitButton {...form}>Submit</FormSubmitButton>
</Form>
);
}
#Validating with Yup
Yup is a popular library for object schema validation. You can easily integrate it with Reakit Form.
import { object, string } from "yup";
import {
unstable_useFormState as useFormState,
unstable_Form as Form,
unstable_FormLabel as FormLabel,
unstable_FormInput as FormInput,
unstable_FormMessage as FormMessage,
unstable_FormSubmitButton as FormSubmitButton,
} from "reakit/Form";
import set from "lodash/set";
const schema = object({
name: string()
.min(2, "Your name is too short!")
.required("How can we be friends without knowing your name?"),
});
function validateWithYup(yupSchema) {
return (values) =>
yupSchema.validate(values, { abortEarly: false }).then(
() => {},
(error) => {
if (error.inner.length) {
throw error.inner.reduce(
(acc, curr) => set(acc, curr.path, curr.message),
{}
);
}
}
);
}
function Example() {
const form = useFormState({
values: { name: "" },
onValidate: validateWithYup(schema),
});
return (
<Form {...form}>
<FormLabel {...form} name="name">
Name
</FormLabel>
<FormInput {...form} name="name" placeholder="John Doe" />
<FormMessage {...form} name="name" />
<FormSubmitButton {...form}>Submit</FormSubmitButton>
</Form>
);
}
#Abstracting
You may find cumbersome having to pass {...form} to every component. Also, repeating FormLabel, FormInput and FormMessage for every form field may sound overly verbose to you.
Reakit is a low level library designed to give you explicit building blocks so you can create anything you want, and design any API you wish. It's easy to go from explicit to implicit.
import React from "react";
import {
unstable_useFormState as useFormState,
unstable_Form as BaseForm,
unstable_FormLabel as FormLabel,
unstable_FormInput as FormInput,
unstable_FormMessage as FormMessage,
unstable_FormSubmitButton as FormSubmitButton,
} from "reakit/Form";
const FormContext = React.createContext();
function Form({ initialValues, onValidate, onSubmit, ...props }) {
const form = useFormState({ values: initialValues, onValidate, onSubmit });
const value = React.useMemo(() => form, Object.values(form));
return (
<FormContext.Provider value={value}>
<BaseForm {...form} {...props} />
</FormContext.Provider>
);
}
function Field({ name, label, ...props }) {
const form = React.useContext(FormContext);
return (
<>
<FormLabel {...form} name={name} label={label} />
<FormInput {...form} {...props} name={name} />
<FormMessage {...form} name={name} />
</>
);
}
function SubmitButton(props) {
const form = React.useContext(FormContext);
return <FormSubmitButton {...form} {...props} />;
}
function Example() {
const onValidate = (values) => {
if (!values.name) {
const errors = {
name: "How can we be friends without knowing your name?",
};
throw errors;
}
};
const onSubmit = (values) => {
alert(JSON.stringify(values, null, 2));
};
return (
<Form
initialValues={{ name: "" }}
onValidate={onValidate}
onSubmit={onSubmit}
>
<Field label="Name" name="name" placeholder="John Doe" />
<SubmitButton>Submit</SubmitButton>
</Form>
);
}
#Accessibility
Formhas roleform.- Clicking on
FormSubmitButtonon a form with errors will move focus to the first failed input. - Clicking on
FormPushButtonwill move focus to the first input in the added row. - Clicking on
FormRemoveButtonwill move focus to the first input in the next row. If there's no next row, it will move focus to the first input in the previous row. If there's no previous row, it will move focus toFormPushButton.
Learn more in Accessibility.
#Composition
Formuses Role.FormCheckboxuses Checkbox.FormGroupuses Group.FormInputuses Input.FormLabeluses Role.FormMessageuses Role.FormPushButtonuses Button.FormRadiouses Radio.FormRadioGroupusesFormGroup.FormRemoveButtonuses Button.FormSubmitButtonuses Button.
Learn more in Composition.
#Props
#useFormState
-
baseIdstringID that will serve as a base for all the items IDs.
-
valuesVForm values.
-
validateOnBlurboolean | undefinedWhether the form should trigger
onValidateon blur. -
validateOnChangeboolean | undefinedWhether the form should trigger
onValidateon change. -
resetOnSubmitSucceedboolean | undefinedWhether the form should reset when it has been successfully submitted.
-
resetOnUnmountboolean | undefinedWhether the form should reset when the component (which called
useFormState) has been unmounted. -
onValidate((values: V) => ValidateReturn<V>) | undefinedA function that receives
form.valuesand return or throw messages. If it returns, messages will be interpreted as successful messages. If it throws, they will be interpreted as errors. It can also return a promise for asynchronous validation. -
onSubmit((values: V) => ValidateReturn<V>) | undefinedA function that receives
form.valuesand performs form submission. If it's triggered byform.submit(),onValidatewill be called before. IfonValidatethrows,onSubmitwill not be called.onSubmitcan also return promises, messages and throw error messages just likeonValidate. The only difference is that this validation will only occur on submit.
#Form
1 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
submit() => voidTriggers form submission (calling
onValidateandonSubmitunderneath).
#FormCheckbox
-
disabledboolean | undefinedSame as the HTML attribute.
-
focusableboolean | undefinedWhen an element is
disabled, it may still befocusable. It works similarly toreadOnlyon form elements. In this case, onlyaria-disabledwill be set. -
checkedboolean | undefinedCheckbox's checked state. If present, it's used instead of
state. -
namePCheckbox's name as in form values.
-
valueArrayValue<DeepPathValue<V, P>> | undefinedCheckbox's value is going to be used when multiple checkboxes share the same state. Checking a checkbox with value will add it to the state array.
6 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
baseIdstringID that will serve as a base for all the items IDs.
-
valuesVForm values.
-
updateUpdate<V>Updates a form value.
-
blur<P extends DeepPath<V, P>>(name: P) => voidSets field's touched state to
true. -
touched{ [P in keyof DeepMap<V, boolean>]?: (DeepMap<V...An object with the same shape as
form.valueswithbooleanvalues. This keeps the touched state of each field. That is, whether a field has been blurred. -
errors{ [P in keyof DeepMap<V, string | void | null>]...An object with the same shape as
form.valueswith string error messages. This stores the error messages throwed byonValidateandonSubmit.
#FormGroup
-
namePFormGroup's name as in form values.
3 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
baseIdstringID that will serve as a base for all the items IDs.
-
touched{ [P in keyof DeepMap<V, boolean>]?: (DeepMap<V...An object with the same shape as
form.valueswithbooleanvalues. This keeps the touched state of each field. That is, whether a field has been blurred. -
errors{ [P in keyof DeepMap<V, string | void | null>]...An object with the same shape as
form.valueswith string error messages. This stores the error messages throwed byonValidateandonSubmit.
#FormInput
-
disabledboolean | undefinedSame as the HTML attribute.
-
focusableboolean | undefinedWhen an element is
disabled, it may still befocusable. It works similarly toreadOnlyon form elements. In this case, onlyaria-disabledwill be set. -
namePFormInput's name as in form values.
6 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
baseIdstringID that will serve as a base for all the items IDs.
-
valuesVForm values.
-
updateUpdate<V>Updates a form value.
-
blur<P extends DeepPath<V, P>>(name: P) => voidSets field's touched state to
true. -
touched{ [P in keyof DeepMap<V, boolean>]?: (DeepMap<V...An object with the same shape as
form.valueswithbooleanvalues. This keeps the touched state of each field. That is, whether a field has been blurred. -
errors{ [P in keyof DeepMap<V, string | void | null>]...An object with the same shape as
form.valueswith string error messages. This stores the error messages throwed byonValidateandonSubmit.
#FormLabel
-
namePFormInput's name as in form values.
-
labelanyLabel can be passed as the
labelprop orchildren.
2 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
baseIdstringID that will serve as a base for all the items IDs.
-
valuesVForm values.
#FormMessage
-
namePFormInput's name as in form values.
4 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
baseIdstringID that will serve as a base for all the items IDs.
-
touched{ [P in keyof DeepMap<V, boolean>]?: (DeepMap<V...An object with the same shape as
form.valueswithbooleanvalues. This keeps the touched state of each field. That is, whether a field has been blurred. -
errors{ [P in keyof DeepMap<V, string | void | null>]...An object with the same shape as
form.valueswith string error messages. This stores the error messages throwed byonValidateandonSubmit. -
messages{ [P in keyof DeepMap<V, string | void | null>]...An object with the same shape as
form.valueswith string messages. This stores the messages returned byonValidateandonSubmit.
#FormPushButton
-
disabledboolean | undefinedSame as the HTML attribute.
-
focusableboolean | undefinedWhen an element is
disabled, it may still befocusable. It works similarly toreadOnlyon form elements. In this case, onlyaria-disabledwill be set. -
namePFormInput's name as in form values. This should point to array value.
-
valueDeepPathValue<V, P> extends (infer U)[] ? U : n...The value that is going to be pushed to
form.values[name].
3 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
baseIdstringID that will serve as a base for all the items IDs.
-
valuesVForm values.
-
push<P extends DeepPath<V, P>>(name: P, value?: Arr...Pushes a new item into
form.values[name], which should be an array.
#FormRadio
-
namePFormRadio's name as in form values.
-
valueP extends DeepPathArray<V, P> ? DeepPathArrayVa...FormRadio's value.
3 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
valuesVForm values.
-
updateUpdate<V>Updates a form value.
-
blur<P extends DeepPath<V, P>>(name: P) => voidSets field's touched state to
true.
#FormRadioGroup
-
namePFormGroup's name as in form values.
3 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
baseIdstringID that will serve as a base for all the items IDs.
-
touched{ [P in keyof DeepMap<V, boolean>]?: (DeepMap<V...An object with the same shape as
form.valueswithbooleanvalues. This keeps the touched state of each field. That is, whether a field has been blurred. -
errors{ [P in keyof DeepMap<V, string | void | null>]...An object with the same shape as
form.valueswith string error messages. This stores the error messages throwed byonValidateandonSubmit.
#FormRemoveButton
-
disabledboolean | undefinedSame as the HTML attribute.
-
focusableboolean | undefinedWhen an element is
disabled, it may still befocusable. It works similarly toreadOnlyon form elements. In this case, onlyaria-disabledwill be set. -
namePFormInput's name as in form values. This should point to array value.
-
indexnumberThe index in
form.values[name]that will be removed.
3 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
baseIdstringID that will serve as a base for all the items IDs.
-
valuesVForm values.
-
remove<P extends DeepPath<V, P>>(name: P, index: numb...Removes
form.values[name][index].
#FormSubmitButton
-
disabledboolean | undefinedSame as the HTML attribute.
-
focusableboolean | undefinedWhen an element is
disabled, it may still befocusable. It works similarly toreadOnlyon form elements. In this case, onlyaria-disabledwill be set.
3 state props
These props are returned by the state hook. You can spread them into this component (
{...state}) or pass them separately. You can also provide these props from your own state logic.
-
submittingbooleanWhether form is submitting or not.
-
baseIdstringID that will serve as a base for all the items IDs.
-
submit() => voidTriggers form submission (calling
onValidateandonSubmitunderneath).