Validation
Provide valuable, actionable feedback to your users with HTML5 form validation, via browser default behaviors or custom styles and JavaScript.
Native HTML5 form validation
Bootstrap scopes the :valid
and :invalid
styles to parent .was-validated
class, usually applied to the <Form>
(you can use the validated
prop as a shortcut). Otherwise, any required field without a value shows up as invalid on page load. This way, you may choose when to activate them (typically after form submission is attempted).
function FormExample() {
const [validated, setValidated] = useState(false); const handleSubmit = (event) => { const form = event.currentTarget; if (form.checkValidity() === false) { event.preventDefault(); event.stopPropagation(); } setValidated(true); }; return ( <Form noValidate validated={validated} onSubmit={handleSubmit}> <Row className="mb-3"> <Form.Group as={Col} md="4" controlId="validationCustom01"> <Form.Label>First name</Form.Label> <Form.Control required type="text" placeholder="First name" defaultValue="Mark"/> <Form.Control.Feedback>Looks good!</Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="4" controlId="validationCustom02"> <Form.Label>Last name</Form.Label> <Form.Control required type="text" placeholder="Last name" defaultValue="Otto"/> <Form.Control.Feedback>Looks good!</Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="4" controlId="validationCustomUsername"> <Form.Label>Username</Form.Label> <InputGroup hasValidation> <InputGroup.Text id="inputGroupPrepend">@</InputGroup.Text> <Form.Control type="text" placeholder="Username" aria-describedby="inputGroupPrepend" required /> <Form.Control.Feedback type="invalid"> Please choose a username. </Form.Control.Feedback> </InputGroup> </Form.Group> </Row> <Row className="mb-3"> <Form.Group as={Col} md="6" controlId="validationCustom03"> <Form.Label>City</Form.Label> <Form.Control type="text" placeholder="City" required /> <Form.Control.Feedback type="invalid"> Please provide a valid city. </Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="3" controlId="validationCustom04"> <Form.Label>State</Form.Label> <Form.Control type="text" placeholder="State" required /> <Form.Control.Feedback type="invalid"> Please provide a valid state. </Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="3" controlId="validationCustom05"> <Form.Label>Zip</Form.Label> <Form.Control type="text" placeholder="Zip" required /> <Form.Control.Feedback type="invalid"> Please provide a valid zip. </Form.Control.Feedback> </Form.Group> </Row> <Form.Group className="mb-3"> <Form.Check required label="Agree to terms and conditions" feedback="You must agree before submitting." feedbackType="invalid" /> </Form.Group> <Button type="submit">Submit form</Button> </Form> ); }
render(<FormExample />);
Form libraries and server-rendered styles
It's often beneficial (especially in React) to handle form validation via a library like Formik, or react-formal. In those cases, isValid
and isInvalid
props can be added to form controls to manually apply validation styles. Below is a quick example integrating with Formik.
const { Formik } = formik;
const schema = yup.object().shape({ firstName: yup.string().required(), lastName: yup.string().required(), username: yup.string().required(), city: yup.string().required(), state: yup.string().required(), zip: yup.string().required(), terms: yup.bool().required().oneOf([true], 'Terms must be accepted'),});
function FormExample() { return ( <Formik validationSchema={schema} onSubmit={console.log} initialValues={{ firstName: 'Mark', lastName: 'Otto', username: '', city: '', state: '', zip: '', terms: false, }} > {({ handleSubmit, handleChange, handleBlur, values, touched, isValid, errors, }) => ( <Form noValidate onSubmit={handleSubmit}> <Row className="mb-3"> <Form.Group as={Col} md="4" controlId="validationFormik01"> <Form.Label>First name</Form.Label> <Form.Control type="text" name="firstName" value={values.firstName} onChange={handleChange} isValid={touched.firstName && !errors.firstName} /> <Form.Control.Feedback>Looks good!</Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="4" controlId="validationFormik02"> <Form.Label>Last name</Form.Label> <Form.Control type="text" name="lastName" value={values.lastName} onChange={handleChange} isValid={touched.lastName && !errors.lastName}/> <Form.Control.Feedback>Looks good!</Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="4" controlId="validationFormikUsername"> <Form.Label>Username</Form.Label> <InputGroup hasValidation> <InputGroup.Text id="inputGroupPrepend">@</InputGroup.Text> <Form.Control type="text" placeholder="Username" aria-describedby="inputGroupPrepend" name="username" value={values.username} onChange={handleChange} isInvalid={!!errors.username}/> <Form.Control.Feedback type="invalid"> {errors.username} </Form.Control.Feedback> </InputGroup> </Form.Group> </Row> <Row className="mb-3"> <Form.Group as={Col} md="6" controlId="validationFormik03"> <Form.Label>City</Form.Label> <Form.Control type="text" placeholder="City" name="city" value={values.city} onChange={handleChange} isInvalid={!!errors.city} /> <Form.Control.Feedback type="invalid"> {errors.city} </Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="3" controlId="validationFormik04"> <Form.Label>State</Form.Label> <Form.Control type="text" placeholder="State" name="state" value={values.state} onChange={handleChange} isInvalid={!!errors.state} /> <Form.Control.Feedback type="invalid"> {errors.state} </Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="3" controlId="validationFormik05"> <Form.Label>Zip</Form.Label> <Form.Control type="text" placeholder="Zip" name="zip" value={values.zip} onChange={handleChange} isInvalid={!!errors.zip} /> <Form.Control.Feedback type="invalid"> {errors.zip} </Form.Control.Feedback> </Form.Group> </Row> <Form.Group className="mb-3"> <Form.Check required name="terms" label="Agree to terms and conditions" onChange={handleChange} isInvalid={!!errors.terms} feedback={errors.terms} feedbackType="invalid" id="validationFormik0" /> </Form.Group> <Button type="submit">Submit form</Button> </Form> )} </Formik> );}
render(<FormExample />);
Tooltips
If your form layout allows it, you can use the tooltip
prop to display validation feedback in a styled tooltip. Be sure to have a parent with position: relative
on it for tooltip positioning. In the example below, our column classes have this already, but your project may require an alternative setup.
const { Formik } = formik;
const schema = yup.object().shape({ firstName: yup.string().required(), lastName: yup.string().required(), username: yup.string().required(), city: yup.string().required(), state: yup.string().required(), zip: yup.string().required(), file: yup.mixed().required(), terms: yup.bool().required().oneOf([true], 'terms must be accepted'),});
function FormExample() { return ( <Formik validationSchema={schema} onSubmit={console.log} initialValues={{ firstName: 'Mark', lastName: 'Otto', username: '', city: '', state: '', zip: '', file: null, terms: false, }} > {({ handleSubmit, handleChange, handleBlur, values, touched, isValid, errors, }) => ( <Form noValidate onSubmit={handleSubmit}> <Row className="mb-3"> <Form.Group as={Col} md="4" controlId="validationFormik101" className="position-relative" > <Form.Label>First name</Form.Label> <Form.Control type="text" name="firstName" value={values.firstName} onChange={handleChange} isValid={touched.firstName && !errors.firstName} /> <Form.Control.Feedback tooltip>Looks good!</Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="4" controlId="validationFormik102" className="position-relative"> <Form.Label>Last name</Form.Label> <Form.Control type="text" name="lastName" value={values.lastName} onChange={handleChange} isValid={touched.lastName && !errors.lastName} /> <Form.Control.Feedback tooltip>Looks good!</Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="4" controlId="validationFormikUsername2"> <Form.Label>Username</Form.Label> <InputGroup hasValidation> <InputGroup.Text id="inputGroupPrepend">@</InputGroup.Text> <Form.Control type="text" placeholder="Username" aria-describedby="inputGroupPrepend" name="username" value={values.username} onChange={handleChange} isInvalid={!!errors.username} /> <Form.Control.Feedback type="invalid" tooltip> {errors.username} </Form.Control.Feedback> </InputGroup> </Form.Group> </Row> <Row className="mb-3"> <Form.Group as={Col} md="6" controlId="validationFormik103" className="position-relative"> <Form.Label>City</Form.Label> <Form.Control type="text" placeholder="City" name="city" value={values.city} onChange={handleChange} isInvalid={!!errors.city} /> <Form.Control.Feedback type="invalid" tooltip> {errors.city} </Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="3" controlId="validationFormik104" className="position-relative"> <Form.Label>State</Form.Label> <Form.Control type="text" placeholder="State" name="state" value={values.state} onChange={handleChange} isInvalid={!!errors.state}/> <Form.Control.Feedback type="invalid" tooltip> {errors.state} </Form.Control.Feedback> </Form.Group> <Form.Group as={Col} md="3" controlId="validationFormik105" className="position-relative" > <Form.Label>Zip</Form.Label> <Form.Control type="text" placeholder="Zip" name="zip" value={values.zip} onChange={handleChange} isInvalid={!!errors.zip} /> <Form.Control.Feedback type="invalid" tooltip> {errors.zip} </Form.Control.Feedback> </Form.Group> </Row> <Form.Group className="position-relative mb-3"> <Form.Label>File</Form.Label> <Form.Control type="file" required name="file" onChange={handleChange} isInvalid={!!errors.file} /> <Form.Control.Feedback type="invalid" tooltip> {errors.file} </Form.Control.Feedback> </Form.Group> <Form.Group className="position-relative mb-3"> <Form.Check required name="terms" label="Agree to terms and conditions" onChange={handleChange} isInvalid={!!errors.terms} feedback={errors.terms} feedbackType="invalid" id="validationFormik106" feedbackTooltip/> </Form.Group> <Button type="submit">Submit form</Button> </Form> )} </Formik> );}
render(<FormExample />);
Input group validation
To properly show rounded corners in an <InputGroup>
with validation, the <InputGroup>
requires the hasValidation prop.
<InputGroup hasValidation> <InputGroup.Text>@</InputGroup.Text> <Form.Control type="text" required isInvalid /> <Form.Control.Feedback type="invalid"> Please choose a username. </Form.Control.Feedback></InputGroup>