Forms
Forms allow users to enter and submit data, and provide them with feedback along the way. Cimpress UI includes many components that integrate with HTML forms.
Submitting form data
Section titled “Submitting form data”How you submit form data depends on your framework, application, and server.
By default, HTML forms are submitted by the browser using a full page refresh.
You can take control of form submission by calling preventDefault
during the onSubmit
event,
and make an API call to submit the data however you like.
Default
Section titled “Default”By default, HTML forms are submitted by the browser using a full page refresh.
You can specify where to submit form data using the action
prop of the Form
component.
Each form field must have a name
prop defined.
import { Button, Form, TextField } from '@cimpress-ui/react';
export function FormExample() { return ( <Form action="https://example.com" method="post"> <TextField label="Name" name="name" /> <Button type="submit">Submit</Button> </Form> );}
Custom (uncontrolled)
Section titled “Custom (uncontrolled)”For uncontrolled forms, you can obtain form data during the onSubmit
event.
Each form field must have a name
prop defined. All values will be serialized to strings.
import { Button, Form, TextField } from '@cimpress-ui/react';
export function FormExample() { return ( <Form onSubmit={(e) => { e.preventDefault();
const formData = new FormData(e.currentTarget);
// Do something with `formData` fetch('https://example.com', { method: 'POST', headers: { 'Content-Type': 'multipart/form-data' }, body: formData, }); }} > <TextField label="Name" name="name" /> <Button type="submit">Submit</Button> </Form> );}
Custom (controlled)
Section titled “Custom (controlled)”In controlled forms, you are in control of the values of each form field.
You can still detect form submission by using the onSubmit
event.
import { Button, Form, TextField } from '@cimpress-ui/react';
export function FormExample() { const [name, setName] = useState('');
return ( <Form onSubmit={(e) => { e.preventDefault();
// Do something with `name` alert(name); }} > <TextField label="Name" name="name" /> <TextField label="Name" value={name} onChange={(value) => setName(value)} /> <Button type="submit">Submit</Button> </Form> );}
Validation
Section titled “Validation”Form validation is important to ensure user input is in an expected format and meets business requirements. Cimpress UI supports native HTML constraint validation, custom validation functions, real-time validation, and integration with server-side validation errors. All invalid fields will block forms from being submitted.
Native constraint validation
Section titled “Native constraint validation”Native HTML constraints allow you to define constraints on each field such as required, minimum and maximum values, text formats such as email addresses, and even custom regular expression patterns. These constraints are checked by the browser when the user commits changes to the value (e.g., on blur) or submits the form.
Error messages are provided by the browser, and are localized using the browser/operating system language setting.
The example below shows a required input field that expects a postcode in a specific format:
import { Button, Form, Stack, TextField } from '@cimpress-ui/react';
export default function NativeValidationExample() { return ( <Form onSubmit={(e) => e.preventDefault()}> <Stack gap={16}> <TextField label="Postcode" name="postcode" isRequired pattern="\d{4} [A-Z]{2}" description="Expected format: 1234 AB." />
<Button type="submit">Submit</Button> </Stack> </Form> );}
See each component’s documentation for more details on the supported validation props.
Custom error messages
Section titled “Custom error messages”As shown above, default error messages are provided by the browser, and are localized in the user’s preferred language.
You can customize these messages by providing a function to the error
prop.
This function receives a list of error strings along with a ValidityState
object describing why the field is invalid.
The example below modifies the previous example with custom error messages:
import { Button, Form, Stack, TextField } from '@cimpress-ui/react';
export default function CustomErrorMessageExample() { return ( <Form onSubmit={(e) => e.preventDefault()}> <Stack gap={16}> <TextField label="Postcode" name="postcode" isRequired pattern="\d{4} [A-Z]{2}" description="Expected format: 1234 AB." error={({ validationDetails }) => validationDetails.valueMissing ? 'Postcode must be provided.' : validationDetails.patternMismatch ? 'Provided postcode has incorrect format.' : '' } />
<Button type="submit">Submit</Button> </Stack> </Form> );}
Custom validation
Section titled “Custom validation”You can define your own validation rules by providing a function to the validate
prop.
This function receives the current field value, and can return a string or array of strings representing one or more error messages.
These are displayed to the user after the value is committed (e.g., on blur).
The example below defines a custom validation error if you input the example postcode in the input field:
import { Button, Form, Stack, TextField } from '@cimpress-ui/react';
export default function CustomValidationExample() { return ( <Form onSubmit={(e) => e.preventDefault()}> <Stack gap={16}> <TextField label="Postcode" name="postcode" isRequired pattern="\d{4} [A-Z]{2}" description="Expected format: 1234 AB. Don't use the example postcode." validate={(value) => (value === '1234 AB' ? "Don't use the example postcode." : '')} />
<Button type="submit">Submit</Button> </Stack> </Form> );}
Real-time validation
Section titled “Real-time validation”By default, validation errors are displayed to the user after the value is committed (e.g., on blur), or when the form is submitted. This avoids confusing the user with irrelevant errors while they are still entering a value.
In some cases, validating in real time can be desirable, such as when meeting password requirements.
This can be accomplished by making the field value controlled, and setting the isInvalid
and error
props accordingly.
Type in the input field below to see validation errors change in real time:
import { Button, Form, Stack, TextField } from '@cimpress-ui/react';import { useState } from 'react';
export default function RealTimeValidationExample() { const [password, setPassword] = useState(''); const errors = [];
if (password.length < 8) { errors.push('Password must be 8 characters or more.'); }
if ((password.match(/[A-Z]/g) ?? []).length < 2) { errors.push('Password must include at least 2 upper case letters.'); }
if ((password.match(/[^a-z]/gi) ?? []).length < 2) { errors.push('Password must include at least 2 symbols.'); }
return ( <Form onSubmit={(e) => e.preventDefault()}> <Stack gap={16}> <TextField label="Password" type="password" isRequired value={password} onChange={setPassword} isInvalid={errors.length > 0} error={errors} />
<Button type="submit">Submit</Button> </Stack> </Form> );}
Server-side validation
Section titled “Server-side validation”Client-side validation is useful to give the user immediate feedback, but it’s only one half of the validation story. Data should also be validated on the backend for security and reliability, and your business logic may include rules which cannot be validated on the frontend.
Cimpress UI supports displaying server validation errors by passing the validationErrors
prop to the Form
component.
This should be set to an object that maps each field’s name prop to a string or array of strings representing one or more errors.
These are displayed to the user as soon as the validationErrors
prop is set, and cleared after the user modifies each field’s value.
Submitting the following example form will simulate a server call that returns validation errors:
import { Button, Form, Stack, TextField } from '@cimpress-ui/react';import { useState, type FormEvent } from 'react';
export default function ServerSideValidationExample() { const [errors, setErrors] = useState<Record<string, string | string[]>>({});
const onSubmit = async (e: FormEvent<HTMLFormElement>) => { e.preventDefault();
const data = new FormData(e.currentTarget); const result = await callServer(data); setErrors(result.errors); };
return ( <Form validationErrors={errors} onSubmit={onSubmit}> <Stack gap={16}> <TextField label="Postcode" name="postcode" /> <Button type="submit">Submit</Button> </Stack> </Form> );}
// Fake server call used in this exampleasync function callServer(_data: FormData) { return { errors: { postcode: 'Provided postcode is out of delivery range', }, };}
The validationErrors
prop can also be used to integrate with server-side schema validation libraries like Zod,
as well as server actions that are slowly becoming available in frameworks like Next.js and Remix.
Focus management
Section titled “Focus management”By default, after a user submits a form with validation errors, the first invalid field will be focused.
If you want to move focus elsewhere, you can prevent the default behavior by calling preventDefault
during the onInvalid
event.