Validation with rules object
To validate form with rules object, provide an object of functions which take field value as an argument and return error message (any React node) or null if field is valid:
import { useForm } from '@mantine/form';
import { NumberInput, TextInput, Button, Box } from '@mantine/core';
function Demo() {
const form = useForm({
initialValues: { name: '', email: '', age: 0 },
// functions will be used to validate values at corresponding key
validate: {
name: (value) => (value.length < 2 ? 'Name must have at least 2 letters' : null),
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
age: (value) => (value < 18 ? 'You must be at least 18 to register' : null),
},
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit(console.log)}>
<TextInput label="Name" placeholder="Name" {...form.getInputProps('name')} />
<TextInput mt="sm" label="Email" placeholder="Email" {...form.getInputProps('email')} />
<NumberInput
mt="sm"
label="Age"
placeholder="Age"
min={0}
max={99}
{...form.getInputProps('age')}
/>
<Button type="submit" mt="sm">
Submit
</Button>
</form>
</Box>
);
}
Rule function arguments
Each form rule receives the following arguments:
value
– value of fieldvalues
– all form valuespath
– field path, for exampleuser.email
orcart.0.price
path
argument can be used to get information about field location relative to other fields,
for example you can get index of array element:
import { useForm } from '@mantine/form';
const form = useForm({
initialValues: { a: [{ b: 1 }, { b: 2 }] },
validate: {
a: {
b: (value, values, path) => (path === 'a.0.b' ? 'error' : null),
},
},
});
Validation based on other form values
You can get all form values as a second rule function argument to perform field validation based on other form values. For example, you can validate that password confirmation is the same as password:
import { useForm } from '@mantine/form';
import { PasswordInput, Group, Button, Box } from '@mantine/core';
function Demo() {
const form = useForm({
initialValues: {
password: 'secret',
confirmPassword: 'sevret',
},
validate: {
confirmPassword: (value, values) =>
value !== values.password ? 'Passwords did not match' : null,
},
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<PasswordInput
label="Password"
placeholder="Password"
{...form.getInputProps('password')}
/>
<PasswordInput
mt="sm"
label="Confirm password"
placeholder="Confirm password"
{...form.getInputProps('confirmPassword')}
/>
<Group justify="flex-end" mt="md">
<Button type="submit">Submit</Button>
</Group>
</form>
</Box>
);
}
Function based validation
Another approach to handle validation is to provide a function to validate
.
Function takes form values as single argument and should return object that contains
errors of corresponding fields. If field is valid or field validation is not required, you can either return null or simply omit it
from the validation results.
import { useForm } from '@mantine/form';
import { Box, TextInput, NumberInput, Button, Group } from '@mantine/core';
function Demo() {
const form = useForm<{ name: string; age: number | undefined }>({
initialValues: { name: '', age: undefined },
validate: (values) => ({
name: values.name.length < 2 ? 'Too short name' : null,
age:
values.age === undefined
? 'Age is required'
: values.age < 18
? 'You must be at least 18'
: null,
}),
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<TextInput label="Name" placeholder="Name" {...form.getInputProps('name')} />
<NumberInput mt="sm" label="Age" placeholder="You age" {...form.getInputProps('age')} />
<Group justify="flex-end" mt="md">
<Button type="submit">Submit</Button>
</Group>
</form>
</Box>
);
}
Schema based validation
Out of the box @mantine/form
supports schema validation with:
You will need to install one of the libraries yourself, as @mantine/form
package does not depend on any of them.
If you do not know what validation library to choose, we recommend choosing zod
as the most modern and developer-friendly library.
zod
import { z } from 'zod';
import { useForm, zodResolver } from '@mantine/form';
import { NumberInput, TextInput, Button, Box, Group } from '@mantine/core';
const schema = z.object({
name: z.string().min(2, { message: 'Name should have at least 2 letters' }),
email: z.string().email({ message: 'Invalid email' }),
age: z.number().min(18, { message: 'You must be at least 18 to create an account' }),
});
function Demo() {
const form = useForm({
validate: zodResolver(schema),
initialValues: {
name: '',
email: '',
age: 18,
},
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<TextInput
withAsterisk
label="Email"
placeholder="example@mail.com"
{...form.getInputProps('email')}
/>
<TextInput
withAsterisk
label="Name"
placeholder="John Doe"
mt="sm"
{...form.getInputProps('name')}
/>
<NumberInput
withAsterisk
label="Age"
placeholder="Your age"
mt="sm"
{...form.getInputProps('age')}
/>
<Group justify="flex-end" mt="xl">
<Button type="submit">Submit</Button>
</Group>
</form>
</Box>
);
}
joi
import Joi from 'joi';
import { useForm, joiResolver } from '@mantine/form';
import { NumberInput, TextInput, Button, Box, Group } from '@mantine/core';
const schema = Joi.object({
name: Joi.string().min(2).message('Name should have at least 2 letters'),
email: Joi.string()
.email({ tlds: { allow: false } })
.message('Invalid email'),
age: Joi.number().min(18).message('You must be at least 18 to create an account'),
});
function Demo() {
const form = useForm({
validate: joiResolver(schema),
initialValues: {
name: '',
email: '',
age: 18,
},
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<TextInput
withAsterisk
label="Email"
placeholder="example@mail.com"
{...form.getInputProps('email')}
/>
<TextInput
withAsterisk
label="Name"
placeholder="John Doe"
mt="sm"
{...form.getInputProps('name')}
/>
<NumberInput
withAsterisk
label="Age"
placeholder="Your age"
mt="sm"
{...form.getInputProps('age')}
/>
<Group justify="flex-end" mt="xl">
<Button type="submit">Submit</Button>
</Group>
</form>
</Box>
);
}
yup
import * as Yup from 'yup';
import { useForm, yupResolver } from '@mantine/form';
import { NumberInput, TextInput, Button, Box, Group } from '@mantine/core';
const schema = Yup.object().shape({
name: Yup.string().min(2, 'Name should have at least 2 letters'),
email: Yup.string().email('Invalid email'),
age: Yup.number().min(18, 'You must be at least 18 to create an account'),
});
function Demo() {
const form = useForm({
validate: yupResolver(schema),
initialValues: {
name: '',
email: '',
age: 18,
},
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<TextInput
withAsterisk
label="Email"
placeholder="example@mail.com"
{...form.getInputProps('email')}
/>
<TextInput
withAsterisk
label="Name"
placeholder="John Doe"
mt="sm"
{...form.getInputProps('name')}
/>
<NumberInput
withAsterisk
label="Age"
placeholder="Your age"
mt="sm"
{...form.getInputProps('age')}
/>
<Group justify="flex-end" mt="xl">
<Button type="submit">Submit</Button>
</Group>
</form>
</Box>
);
}
superstruct
import { useForm, superstructResolver } from '@mantine/form';
import { NumberInput, TextInput, Button, Box, Group } from '@mantine/core';
import * as s from 'superstruct';
import isEmail from 'is-email';
const emailString = s.define('email', isEmail);
const schema = s.object({
name: s.size(s.string(), 2, 30),
email: emailString,
age: s.min(s.number(), 18)
});
function Demo() {
const form = useForm({
validate: superstructResolver(schema),
initialValues: {
name: '',
email: '',
age: 18
}
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<TextInput
withAsterisk
label="Email"
placeholder="example@mail.com"
{...form.getInputProps('email')}
/>
<TextInput
withAsterisk
label="Name"
placeholder="John Doe"
mt="sm"
{...form.getInputProps('name')}
/>
<NumberInput
withAsterisk
label="Age"
placeholder="Your age"
mt="sm"
{...form.getInputProps('age')}
/>
<Group justify="flex-end" mt="xl">
<Button type="submit">Submit</Button>
</Group>
</form>
</Box>
);
}
Validate fields on change
To validate all fields on change set validateInputOnChange
option to true
:
const form = useForm({ validateInputOnChange: true });
import { useForm } from '@mantine/form';
import { NumberInput, TextInput, Button, Box } from '@mantine/core';
function Demo() {
const form = useForm({
validateInputOnChange: true,
initialValues: { name: '', email: '', age: 0 },
// functions will be used to validate values at corresponding key
validate: {
name: (value) => (value.length < 2 ? 'Name must have at least 2 letters' : null),
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
age: (value) => (value < 18 ? 'You must be at least 18 to register' : null),
},
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit(console.log)}>
<TextInput label="Name" placeholder="Name" {...form.getInputProps('name')} />
<TextInput mt="sm" label="Email" placeholder="Email" {...form.getInputProps('email')} />
<NumberInput
mt="sm"
label="Age"
placeholder="Age"
min={0}
max={99}
{...form.getInputProps('age')}
/>
<Button type="submit" mt="sm">
Submit
</Button>
</form>
</Box>
);
}
You can also provide an array of fields paths to validate only those values:
import { FORM_INDEX, useForm } from '@mantine/form';
const form = useForm({ validateInputOnChange: ['name', 'email', `jobs.${FORM_INDEX}.title`] });
import { useForm, FORM_INDEX } from '@mantine/form';
import { NumberInput, TextInput, Button, Box } from '@mantine/core';
function Demo() {
const form = useForm({
validateInputOnChange: [
'email',
'name',
// use FORM_INDEX to reference fields indices
`jobs.${FORM_INDEX}.title`,
],
initialValues: { name: '', email: '', age: 0, jobs: [{ title: '' }, { title: '' }] },
// functions will be used to validate values at corresponding key
validate: {
name: (value) => (value.length < 2 ? 'Name must have at least 2 letters' : null),
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
age: (value) => (value < 18 ? 'You must be at least 18 to register' : null),
jobs: {
title: (value) => (value.length < 2 ? 'Job must have at least 2 letters' : null),
},
},
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit(console.log)}>
<TextInput label="Name" placeholder="Name" {...form.getInputProps('name')} />
<TextInput mt="sm" label="Email" placeholder="Email" {...form.getInputProps('email')} />
<NumberInput
mt="sm"
label="Age"
placeholder="Age"
min={0}
max={99}
{...form.getInputProps('age')}
/>
<TextInput
mt="sm"
label="Job 1"
placeholder="Job 1"
{...form.getInputProps('jobs.0.title')}
/>
<TextInput
mt="sm"
label="Job 2"
placeholder="Job 2"
{...form.getInputProps('jobs.1.title')}
/>
<Button type="submit" mt="sm">
Submit
</Button>
</form>
</Box>
);
}
Validate fields on blur
To validate all fields on blur set validateInputOnBlur
option to true
:
const form = useForm({ validateInputOnBlur: true });
import { useForm } from '@mantine/form';
import { NumberInput, TextInput, Button, Box } from '@mantine/core';
function Demo() {
const form = useForm({
validateInputOnBlur: true,
initialValues: { name: '', email: '', age: 0 },
// functions will be used to validate values at corresponding key
validate: {
name: (value) => (value.length < 2 ? 'Name must have at least 2 letters' : null),
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
age: (value) => (value < 18 ? 'You must be at least 18 to register' : null),
},
});
return (
<Box maw={320} mx="auto">
<form onSubmit={form.onSubmit(console.log)}>
<TextInput label="Name" placeholder="Name" {...form.getInputProps('name')} />
<TextInput mt="sm" label="Email" placeholder="Email" {...form.getInputProps('email')} />
<NumberInput
mt="sm"
label="Age"
placeholder="Age"
min={0}
max={99}
{...form.getInputProps('age')}
/>
<Button type="submit" mt="sm">
Submit
</Button>
</form>
</Box>
);
}
You can also provide an array of fields paths to validate only those values:
import { FORM_INDEX, useForm } from '@mantine/form';
const form = useForm({ validateInputOnBlur: ['name', 'email', `jobs.${FORM_INDEX}.title`] });
import { useForm, FORM_INDEX } from '@mantine/form';
import { NumberInput, TextInput, Button, Box } from '@mantine/core';
function Demo() {
const form = useForm({
validateInputOnBlur: [
'email',
'name',
// use FORM_INDEX to reference fields indices
`jobs.${FORM_INDEX}.title`,
],
initialValues: { name: '', email: '', age: 0, jobs: [{ title: '' }, { title: '' }] },
// functions will be used to validate values at corresponding key
validate: {
name: (value) => (value.length < 2 ? 'Name must have at least 2 letters' : null),
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
age: (value) => (value < 18 ? 'You must be at least 18 to register' : null),
jobs: {
title: (value) => (value.length < 2 ? 'Job must have at least 2 letters' : null),
},
},
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit(console.log)}>
<TextInput label="Name" placeholder="Name" {...form.getInputProps('name')} />
<TextInput mt="sm" label="Email" placeholder="Email" {...form.getInputProps('email')} />
<NumberInput
mt="sm"
label="Age"
placeholder="Age"
min={0}
max={99}
{...form.getInputProps('age')}
/>
<TextInput
mt="sm"
label="Job 1"
placeholder="Job 1"
{...form.getInputProps('jobs.0.title')}
/>
<TextInput
mt="sm"
label="Job 2"
placeholder="Job 2"
{...form.getInputProps('jobs.1.title')}
/>
<Button type="submit" mt="sm">
Submit
</Button>
</form>
</Box>
);
}
Clear field error on change
By default, field error is cleared when value changes. To change this, set clearInputErrorOnChange
to false
:
const form = useForm({ clearInputErrorOnChange: false });
import { TextInput, Checkbox, Button, Group, Box } from '@mantine/core';
import { useForm } from '@mantine/form';
function Demo() {
const form = useForm({
clearInputErrorOnChange: false,
initialValues: {
email: '',
termsOfService: false,
},
validate: {
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
},
});
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<TextInput
withAsterisk
label="Email"
placeholder="your@email.com"
{...form.getInputProps('email')}
/>
<Checkbox
mt="md"
label="I agree to sell my privacy"
{...form.getInputProps('termsOfService', { type: 'checkbox' })}
/>
<Group justify="flex-end" mt="md">
<Button type="submit">Submit</Button>
</Group>
</form>
</Box>
);
}
Validation in onSubmit handler
form.onSubmit
accepts two arguments: first argument is handleSubmit
function that will be called with form values, when validation
was completed without errors, second argument is handleErrors
function, it is called with errors object when validation was completed with errors.
You can use handleErrors
function to perform certain actions when user tries to submit form without values,
for example, you can show a notification:
import { useForm } from '@mantine/form';
import { TextInput, Button, Box } from '@mantine/core';
import { notifications } from '@mantine/notifications';
function Demo() {
const form = useForm({
initialValues: { name: '', email: '' },
validate: {
name: (value) => (value.length < 2 ? 'Name must have at least 2 letters' : null),
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
},
});
const handleError = (errors: typeof form.errors) => {
if (errors.name) {
notifications.show({ message: 'Please fill name field', color: 'red' });
} else if (errors.email) {
notifications.show({ message: 'Please provide a valid email', color: 'red' });
}
};
return (
<Box maw={340} mx="auto">
<form onSubmit={form.onSubmit(console.log, handleError)}>
<TextInput label="Name" placeholder="Name" {...form.getInputProps('name')} />
<TextInput mt="sm" label="Email" placeholder="Email" {...form.getInputProps('email')} />
<Button type="submit" mt="sm">
Submit
</Button>
</form>
</Box>
);
}
isValid handler
form.isValid
performs form validation with given validation functions, rules object or schema, but unlike
form.validate
it does not set form.errors
and just returns boolean value that indicates whether form is valid.
import { useForm } from '@mantine/form';
const form = useForm({
initialValues: { name: '', age: 0 },
validate: {
name: (value) => (value.trim().length < 2 ? 'Too short' : null),
age: (value) => (value < 18 ? 'Too young' : null),
},
});
// get validation status of all values
form.isValid(); // -> false
// get validation status of field
form.isValid('name'); // -> false
Build fully functional accessible web applications faster than ever