Looking for Reakit's successor?Visit Ariakit
Skip to main content
Reakit
DocumentationNewsletter
GitHub
GitHub

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.

Preferences
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.

Choice
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

  • Form has role form.
  • Clicking on FormSubmitButton on a form with errors will move focus to the first failed input.
  • Clicking on FormPushButton will move focus to the first input in the added row.
  • Clicking on FormRemoveButton will 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 to FormPushButton.

Learn more in Accessibility.

#Composition

  • Form uses Role.
  • FormCheckbox uses Checkbox.
  • FormGroup uses Group.
  • FormInput uses Input.
  • FormLabel uses Role.
  • FormMessage uses Role.
  • FormPushButton uses Button.
  • FormRadio uses Radio.
  • FormRadioGroup uses FormGroup.
  • FormRemoveButton uses Button.
  • FormSubmitButton uses Button.

Learn more in Composition.

#Props

#useFormState

  • baseId string

    ID that will serve as a base for all the items IDs.

  • values V

    Form values.

  • validateOnBlur boolean | undefined

    Whether the form should trigger onValidate on blur.

  • validateOnChange boolean | undefined

    Whether the form should trigger onValidate on change.

  • resetOnSubmitSucceed boolean | undefined

    Whether the form should reset when it has been successfully submitted.

  • resetOnUnmount boolean | undefined

    Whether the form should reset when the component (which called useFormState) has been unmounted.

  • onValidate ((values: V) => ValidateReturn<V>) | undefined

    A function that receives form.values and 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>) | undefined

    A function that receives form.values and performs form submission. If it's triggered by form.submit(), onValidate will be called before. If onValidate throws, onSubmit will not be called. onSubmit can also return promises, messages and throw error messages just like onValidate. 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 () => void

    Triggers form submission (calling onValidate and onSubmit underneath).

#FormCheckbox

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

  • checked boolean | undefined

    Checkbox's checked state. If present, it's used instead of state.

  • name P

    Checkbox's name as in form values.

  • value ArrayValue<DeepPathValue<V, P>> | undefined

    Checkbox'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.

  • baseId string

    ID that will serve as a base for all the items IDs.

  • values V

    Form values.

  • update Update<V>

    Updates a form value.

  • blur <P extends DeepPath<V, P>>(name: P) => void

    Sets field's touched state to true.

  • touched { [P in keyof DeepMap<V, boolean>]?: (DeepMap<V...

    An object with the same shape as form.values with boolean values. 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.values with string error messages. This stores the error messages throwed by onValidate and onSubmit.

#FormGroup

  • name P

    FormGroup'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.

  • baseId string

    ID 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.values with boolean values. 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.values with string error messages. This stores the error messages throwed by onValidate and onSubmit.

#FormInput

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

  • name P

    FormInput'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.

  • baseId string

    ID that will serve as a base for all the items IDs.

  • values V

    Form values.

  • update Update<V>

    Updates a form value.

  • blur <P extends DeepPath<V, P>>(name: P) => void

    Sets field's touched state to true.

  • touched { [P in keyof DeepMap<V, boolean>]?: (DeepMap<V...

    An object with the same shape as form.values with boolean values. 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.values with string error messages. This stores the error messages throwed by onValidate and onSubmit.

#FormLabel

  • name P

    FormInput's name as in form values.

  • label any

    Label can be passed as the label prop or children.

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.

  • baseId string

    ID that will serve as a base for all the items IDs.

  • values V

    Form values.

#FormMessage

  • name P

    FormInput'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.

  • baseId string

    ID 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.values with boolean values. 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.values with string error messages. This stores the error messages throwed by onValidate and onSubmit.

  • messages { [P in keyof DeepMap<V, string | void | null>]...

    An object with the same shape as form.values with string messages. This stores the messages returned by onValidate and onSubmit.

#FormPushButton

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

  • name P

    FormInput's name as in form values. This should point to array value.

  • value DeepPathValue<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.

  • baseId string

    ID that will serve as a base for all the items IDs.

  • values V

    Form 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

  • name P

    FormRadio's name as in form values.

  • value P 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.

  • values V

    Form values.

  • update Update<V>

    Updates a form value.

  • blur <P extends DeepPath<V, P>>(name: P) => void

    Sets field's touched state to true.

#FormRadioGroup

  • name P

    FormGroup'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.

  • baseId string

    ID 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.values with boolean values. 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.values with string error messages. This stores the error messages throwed by onValidate and onSubmit.

#FormRemoveButton

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

  • name P

    FormInput's name as in form values. This should point to array value.

  • index number

    The 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.

  • baseId string

    ID that will serve as a base for all the items IDs.

  • values V

    Form values.

  • remove <P extends DeepPath<V, P>>(name: P, index: numb...

    Removes form.values[name][index].

#FormSubmitButton

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will 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.

  • submitting boolean

    Whether form is submitting or not.

  • baseId string

    ID that will serve as a base for all the items IDs.

  • submit () => void

    Triggers form submission (calling onValidate and onSubmit underneath).

Powered by Vercel

Released under the MIT License

Copyright © 2017-2023 Diego Haz