An opinionated form library.
- TypeScript + React Final Form
- Strict type safety
- Validation built with io-ts
- Accessible by default
- By default we render accessible form labels and validation feedback (with
aria-invalid
andaria-labelledby
) - We follow the guidance from https://www.tpgi.com/required-attribute-requirements/
- We use the
novalidate
attribute on theform
element to disable browsers’ client-side validation. Instead, we implement custom validation and accessible error messaging. - To ensure most screen readers won’t default to announcing required form controls as invalid, we use
aria-invalid="false"
. We update this totrue
if the current value (or lack thereof) of a required control does not pass validation. - We use
aria-describedby
on required form controls to point to the element that contains the inline error message. - We wait until the control has lost focus before marking a form control as invalid and displaying the inline error message. We do this by defaulting React Final Form's
validateOnBlur
option to true. See final-form/final-form#250. As a consequence, we include work-arounds for final-form/final-form#213 and final-form/react-final-form#458
- We use the
- After a failed form submission, we move focus to the first invalid form element (using https://github.com/oaf-project/oaf-side-effects)
- We follow the advice from https://webaim.org/techniques/formvalidation/
- By default we render accessible form labels and validation feedback (with
# yarn
yarn add oaf-react-final-form
# npm
npm install oaf-react-final-form
// First, we define the form codec using io-ts.
// See https://github.com/gcanti/io-ts#the-idea
//
// `formCodec` is just a convenience function over the top of
// `intersection`, `type`, `partial` and `readonly` from io-ts.
// See https://github.com/gcanti/io-ts#mixing-required-and-optional-props
const codec = formCodec({
optional: {
foo: t.string,
},
});
// We derive React components for our form elements from the form codec. This
// gives us some type-safety benefits when rendering these form elements (below).
const { Form, Input, Select } = elementsForCodec(codec);
type FormData = t.TypeOf<typeof codec>;
const onSubmit = (formData: FormData): SubmissionResponse<FormData> => {
// Here we are guaranteed that `formData` has been parsed by our form codec.
// We can return submission errors here if necessary.
return undefined;
};
const form = (
<Form onSubmit={onSubmit}>
{/*
The `name` attr must be one of the values from the form codec.
The `type` and `required` attrs must be compatible with the corresponding property from the form codec.
* Because `foo` is optional in the codec, `required` must be either undefined or false.
* Because `foo` is a string in the codec, `type` cannot be one of the numeric input types (`number` or `range`).
*/}
<Input label="foo" name="foo" type="text" />
</Form>
);
See Form.test.tsx for more examples.
- Try not to use
input type="number"
. See https://technology.blog.gov.uk/2020/02/24/why-the-gov-uk-design-system-team-changed-the-input-type-for-numbers/ for why and an alternative.