Chakra UI and React Hook Form – How to Build Beautiful Forms
Forms are a crucial part of many web applications, but building them can often be a tedious process. You have to set up the proper markup, manage form state, implement validation, style the form components, and ensure accessibility – all while providing a smooth user experience.
Fortunately, by combining two powerful libraries – Chakra UI and React Hook Form – you can greatly simplify the process of building professional, polished forms in your React apps. In this post, we‘ll walk through how to leverage these tools effectively to create attractive, user-friendly forms with minimal hassle.
Why Use Chakra UI for Forms?
Chakra UI is a component library for React that allows you to quickly build accessible, customizable user interfaces. It provides a set of prebuilt, composable components that handle many common UI patterns out of the box, including forms and form elements.
Some key advantages of using Chakra UI for form building include:
- Accessible components that follow WAI-ARIA standards
- Highly customizable component styles via style props
- Intuitive, mobile-first responsive design system
- Robust theming and design token features
- Well-documented APIs with strong TypeScript support
By starting with Chakra UI form components as a base, we can create professional-looking forms without having to write a lot of custom CSS or reinvent the wheel when it comes to fundamental UI concerns.
What is React Hook Form?
React Hook Form is a lightweight library that simplifies the process of managing form state and validation in React function components. It leverages React hooks to provide a declarative, intuitive API for handling common form concerns like input registration, submission, validation, and error handling.
Some notable features of React Hook Form include:
- Simple, uncontrolled inputs by default (no onChange handlers needed)
- Built-in validation rules and error messaging
- Seamless integration with UI libraries like Chakra UI
- Improved performance by minimizing re-renders
- Extensive customization options
React Hook Form eliminates a lot of the repetitive boilerplate typically involved with building forms in React, allowing us to focus on the core functionality of our form.
Building a Basic Form
Let‘s walk through the process of building a simple form using Chakra UI and React Hook Form together. We‘ll create a basic login form with fields for username and password.
First, make sure you have Chakra UI and React Hook Form installed:
npm i @chakra-ui/react react-hook-form
Then, import the necessary components and hooks in your form component file:
import { Button, FormControl, FormLabel, Input, VStack } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
Now, let‘s set up the basic structure of our form:
function LoginForm() {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<VStack spacing={4}>
<FormControl id="username">
<FormLabel>Username</FormLabel>
<Input type="text" {...register(‘username‘)} />
</FormControl>
<FormControl id="password">
<FormLabel>Password</FormLabel>
<Input type="password" {...register(‘password‘)} />
</FormControl>
<Button type="submit" colorScheme="blue">
Log In
</Button>
</VStack>
</form>
);
}
Here‘s what‘s happening:
-
We use the
useForm
hook from React Hook Form to handle our form state and submission. This gives us aregister
function to register our form inputs and ahandleSubmit
function to handle the form submission. -
We define our form UI using Chakra UI components like
FormControl
,FormLabel
,Input
, andButton
. TheVStack
component is used to vertically stack the form elements with consistent spacing. -
To register the form inputs with React Hook Form, we spread the
register
function as props on theInput
components, specifying the name of each input. -
We pass our
onSubmit
handler tohandleSubmit
on the form. This function will receive the form data as an argument when the form is submitted. -
For now, we simply log out the form data to the console on submit.
With just this basic setup, we already have a working form that captures user input and handles submission. But we can still enhance it further with validation and better styling.
Adding Form Validation
Proper form validation is essential for maintaining data integrity and guiding users to enter data in the expected format. React Hook Form makes it easy to add validation to our forms.
To define validation rules, we pass a configuration object to the register
function with our desired rules. Let‘s add some validation to our login form:
<FormControl id="username">
<FormLabel>Username</FormLabel>
<Input
type="text"
{...register(‘username‘, {
required: ‘Username is required‘,
minLength: { value: 4, message: ‘Username must be at least 4 characters‘ },
})}
/>
</FormControl>
<FormControl id="password">
<FormLabel>Password</FormLabel>
<Input
type="password"
{...register(‘password‘, {
required: ‘Password is required‘,
minLength: { value: 8, message: ‘Password must be at least 8 characters‘ },
})}
/>
</FormControl>
We‘ve added validation rules for both the username and password fields:
- The
required
rule specifies that the field must have a value. We can provide a custom error message string. - The
minLength
rule specifies a minimum character length for the field value. We provide an object with thevalue
and a custommessage
.
React Hook Form will automatically track the validation state of our inputs and prevent form submission if there are any validation errors. However, we still need to display those error messages to the user.
We can access the validation errors via the formState.errors
object returned by useForm
. Let‘s display our error messages using Chakra UI‘s FormErrorMessage
component:
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
// ...
return (
<form onSubmit={handleSubmit(onSubmit)}>
<VStack spacing={4}>
<FormControl id="username" isInvalid={errors.username}>
<FormLabel>Username</FormLabel>
<Input
type="text"
{...register(‘username‘, {
required: ‘Username is required‘,
minLength: { value: 4, message: ‘Username must be at least 4 characters‘ },
})}
/>
<FormErrorMessage>{errors.username?.message}</FormErrorMessage>
</FormControl>
<FormControl id="password" isInvalid={errors.password}>
<FormLabel>Password</FormLabel>
<Input
type="password"
{...register(‘password‘, {
required: ‘Password is required‘,
minLength: { value: 8, message: ‘Password must be at least 8 characters‘ },
})}
/>
<FormErrorMessage>{errors.password?.message}</FormErrorMessage>
</FormControl>
<Button type="submit" colorScheme="blue">
Log In
</Button>
</VStack>
</form>
);
}
We‘ve made a few key changes:
-
We destructure
errors
fromformState
returned byuseForm
. This will contain any validation error messages. -
We pass
isInvalid={errors.username}
andisInvalid={errors.password}
to theFormControl
s to conditionally apply error styling. -
We display the error message for each field using
FormErrorMessage
, accessing it viaerrors.username?.message
anderrors.password?.message
. The?.
optional chaining operator ensures we don‘t get an error if theerrors
object doesn‘t contain an entry for that field.
Now our form displays inline validation errors whenever a field has an invalid value, providing clear feedback to the user.
Styling with Chakra UI
While our form is functional, it‘s a bit plain looking. We can easily spruce it up with some additional styling using Chakra UI‘s style props.
Chakra UI components are highly customizable directly through props, allowing us to change styles without writing custom CSS classes. We can leverage this to improve the appearance of our form:
<VStack spacing={8} width="100%" maxWidth="md" margin="auto" mt={8}>
<Heading as="h1" size="xl" textAlign="center">
Log In
</Heading>
<Box
p={8}
borderWidth={1}
borderRadius={8}
boxShadow="lg"
bg="white"
width="100%"
>
<form onSubmit={handleSubmit(onSubmit)}>
<VStack spacing={6}>
<FormControl id="username" isInvalid={errors.username}>
<FormLabel>Username</FormLabel>
<Input
type="text"
{...register(‘username‘, {
required: ‘Username is required‘,
minLength: { value: 4, message: ‘Username must be at least 4 characters‘ },
})}
/>
<FormErrorMessage>{errors.username?.message}</FormErrorMessage>
</FormControl>
<FormControl id="password" isInvalid={errors.password}>
<FormLabel>Password</FormLabel>
<Input
type="password"
{...register(‘password‘, {
required: ‘Password is required‘,
minLength: { value: 8, message: ‘Password must be at least 8 characters‘ },
})}
/>
<FormErrorMessage>{errors.password?.message}</FormErrorMessage>
</FormControl>
<Button type="submit" colorScheme="blue" size="lg" width="full">
Log In
</Button>
</VStack>
</form>
</Box>
</VStack>
Let‘s break down the changes:
-
We wrap our whole form in a
VStack
to center it on the page and limit its max width. Thespacing
,width
,maxWidth
,margin
, andmt
props control the layout and spacing. -
We add a heading to the form using the
Heading
component. -
We wrap the form in a
Box
component to give it a border, border radius, box shadow, and background color, making it visually distinct from the page background. Thep
prop adds padding around the form contents. -
We tweak the
spacing
of theVStack
wrapping the form fields to adjust the vertical gap between fields. -
We make the submit button larger (
size="lg"
) and set its width to"full"
to fill the width of the form.
With just a handful of Chakra UI components and style props, our form now has a much more polished, professional look.
Accessibility Considerations
When building forms, it‘s important to ensure they are accessible to all users, including those using assistive technologies like screen readers. Chakra UI and React Hook Form both help us build forms with good accessibility by default, but there are a few key things to keep in mind:
- Use semantic HTML elements like
<form>
,<label>
,<input>
, and<button>
to provide meaning and structure. - Provide a unique, descriptive
id
for each form field that matches its associated<label>
. Chakra UI‘sFormControl
component handles this for us. - Ensure all form fields have a visible label. Again, Chakra UI‘s
FormLabel
takes care of this. - Provide clear error messages for validation errors, associated with the relevant form field. We achieve this with Chakra UI‘s
FormErrorMessage
. - Ensure form elements have sufficient color contrast. Chakra UI‘s default theme is designed with accessibility in mind.
- Make sure the form can be navigated and submitted using only a keyboard. React Hook Form‘s uncontrolled inputs allow this by default.
By using Chakra UI and React Hook Form and following these guidelines, we can create forms that are usable and accessible for all users.
Conclusion
Building forms in React doesn‘t have to be a chore. By leveraging the power of Chakra UI and React Hook Form together, we can create professional, accessible, and user-friendly forms with minimal boilerplate and effort.
Chakra UI provides a solid foundation of customizable, accessible form components that allow us to quickly scaffold out our form UI. React Hook Form simplifies the process of managing form state, validation, and submission, eliminating common pain points.
By combining these two libraries and following some form building best practices, we can create polished forms that provide a great user experience while also being easy to develop and maintain.
So next time you need to build a form in your React app, consider giving Chakra UI and React Hook Form a try. Happy form building!