A contact form is a conversion event. Every design decision either increases or decreases the chance someone fills it out. Here's how to build one that works.
The minimum viable form
- Name — first name only is enough. Full name adds friction without value.
- Email — required. This is how you reply.
- Message — a textarea with 4–5 rows. Placeholder text like "Tell us about your project" guides the input.
- Submit button — explicit bg color, full width on mobile, text like "Send Message" not "Submit".
Design rules
- Card container: rounded border, padding, subtle shadow. The form should look like a distinct, intentional element — not floating inputs.
- Labels above inputs. Placeholder-only fields confuse users once they start typing. Always visible labels.
- Full-width inputs. Narrow input fields on a wide page look broken.
w-fullon every field. - Submit button contrast. The button must have explicit bg and text colors.
bg-primary text-whiteminimum. Never rely on browser defaults.
After submit
- Show a success message. Replace the form with "Thanks! We'll be in touch within 24 hours." Don't just clear the form — the user needs confirmation.
- Send a notification. Discord webhook for instant team alerts + Resend email for a formatted copy. Use
Promise.allSettledso one failing doesn't break the other. - Track the event. Push to dataLayer for GTM:
{ event: "form_submit", form_name: "contact" }.
Anti-patterns to avoid
- CAPTCHA on a low-traffic form. You don't need reCAPTCHA until you have a spam problem. It reduces real submissions by 10–20%.
- Required phone number. Most people don't want to be called. Make phone optional or remove it.
- Dropdown for "How did you hear about us?" It adds friction and the data is unreliable. Ask in the follow-up email instead.