This guide will show you how to make a nice-looking and elegant form with NextJS, Tailwind CSS, and TypeScript. It works perfectly with standard NextJS pages and the new app directory, but can be used on standalone React too.
On top of that, your form will be able to handle spam submissions, send emails, and sync with your CRM/database.
In case you're starting a brand new project, you'll need some initial steps. One of the most straightforward ways is to use the create-next-app package. To start:
mkdir ~/nextjs-project && cd ~/nextjs-project
npx create-next-app@latest .
npm run dev
Use your favorite code editor to work with files in ~/nextjs-project. You will be able to make a contact form there.
Create a new file called ContactForm.tsx in the src/components/ folder. You can copy-paste the code below to get started quickly. Check HeroTofu's extensive forms library for other ready to use forms.
1function ContactForm() {
2 return (
3 <div className="md:w-96 md:max-w-full w-full mx-auto">
4 <div className="sm:rounded-md p-6 border border-gray-300">
5 <form method="POST" action={FORM_ENDPOINT}>
6 <label className="block mb-6">
7 <span className="text-gray-700">Your name</span>
8 <input
9 type="text"
10 name="name"
11 className=" focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 block w-full mt-1 border-gray-300 rounded-md shadow-sm"
12 placeholder="Joe Bloggs"
13 />
14 </label>
15 <label className="block mb-6">
16 <span className="text-gray-700">Email address</span>
17 <input
18 name="email"
19 type="email"
20 className=" focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 block w-full mt-1 border-gray-300 rounded-md shadow-sm"
21 placeholder="joe.bloggs@example.com"
22 required
23 />
24 </label>
25 <label className="block mb-6">
26 <span className="text-gray-700">Message</span>
27 <textarea
28 name="message"
29 className=" focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 block w-full mt-1 border-gray-300 rounded-md shadow-sm"
30 rows={3}
31 placeholder="Tell us what you're thinking about..."
32 ></textarea>
33 </label>
34 <div className="mb-2">
35 <button
36 type="submit"
37 className=" focus:shadow-outline hover:bg-indigo-800 h-10 px-5 text-indigo-100 transition-colors duration-150 bg-indigo-700 rounded-lg"
38 >
39 Contact Us
40 </button>
41 </div>
42 </form>
43 </div>
44 </div>
45 );
46}
47
48export default ContactForm;
To effortlessly handle form submissions, you can use the herotofu-react package. It will do all the form submission process work for you.
1npm install --save herotofu-react
Then, adjust the Form component to use the hook with new `formState` and `getFormSubmitHandler`.
1function ContactForm() {
2 // TODO - update to the correct endpoint
3 const { formState, getFormSubmitHandler } = useFormData('https://herotofu.com/start');
4
5 const onSubmitCallback = ({ status, data }) => {
6 // Sync the success/error status with 3rd party services easily. For example, analytics or CRM
7 console.log(`The form finished submission in status: ${status} and data: ${JSON.stringify(data)}`);
8 };
9
10 const statusBg =
11 formState.status === 'not_initialized'
12 ? ''
13 : formState.status === 'error'
14 ? 'bg-red-600 text-red-100'
15 : formState.status === 'loading'
16 ? 'bg-yellow-600 text-yellow-100'
17 : 'bg-green-600 text-green-100';
18
19 return (
20 <div className="md:w-96 md:max-w-full w-full mx-auto">
21 <div className="sm:rounded-md p-6 border border-gray-300">
22 {!!formState.status && (
23 <div className={`mb-4 py-2 ${statusBg}`}>
24 Current form status is: {formState.status}
25 {formState.status === 'error' && <> {formState.error.message}</>}
26 </div>
27 )}
28 <form onSubmit={getFormSubmitHandler(onSubmitCallback, injectedData)}>
29
30 // Here goes the rest of the code...
Open any page you want to see the form and insert the newly created form component. It will work for old pages and the new app directory (don't forget the "use client" directive if using app directory).
1import ContactForm from '../components/ContactForm';
2
3function Page() {
4 return <ContactForm />;
5}
6
7export default Page;
Head over to herotofu.com and create an account. It will handle all the boring and complex form submission process work for you. You'll get 14 days of the free trial at first, and later you can leave it with the free forever plan. For vast majority of people, free plan is usually more than enough.
HeroTofu registration is straightforward. Fill in the basic fields and then confirm your email address.
Once you have confirmed your email address, go to app.herotofu.com/forms to create your first form. Fill in the form name and add your preferred email address where you'd like to receive your form submits. Slack and Zapier are also options, but you need to pay for them once the trial is over.
You'll get the form endpoint URL once you hit submit, so remember to copy it.
Once again, open the ContactForm.js file and fill in the form endpoint URL. You need to change the passed string to the useFormData() at the top of the component. It should look like this.
1import { useFormData } from 'herotofu-react';
2
3const ContactForm = () => {
4 const { formState, getFormSubmitHandler } = useFormData('ENDPOINT_URL_OR_HEROTOFU_FORM_ID');
5
6 // The rest of the code...
Done! Go ahead and test your form submission! You don't need to do any backend email work, as HeroTofu will handle everything.
HeroTofu accepts regular form submissions, multipart file uploads, and JSON payloads. So you can create javascript objects and send them via fetch() to the endpoint. When you send a JSON payload, don't forget to set the correct JSON headers, and HeroTofu will respond with the needed status codes. Here's what it could look like in practice.
1// the regular useFormData is for forms
2// useJsonData is a helper hook that sends JSON payloads to HeroTofu
3// however, it uses the same form endpoint
4import { useJsonData } from 'herotofu-react';
5
6function useEmailSubscribe() {
7 const { dataState: subscribeState, sendData } = useJsonData(HEROTOFU_FORM_ID);
8
9 const subscribe = (emailToSubscribe: string) => {
10 const email = String(emailToSubscribe).trim();
11 sendData(undefined, { email });
12 };
13
14 return { subscribeState, subscribe };
15}
16
17export default useEmailSubscribe;
Treat it as a REST API because as long as you're sending POST, you'll be good to go. Your react form submission will reach your inbox.