Contact Form
Complete contact form with validation, loading states, and success confirmation page.
ReactTailwind CSSTypeScriptResponsiveForm
Live Preview
Interactive
Contact Form.tsxLive Code
"use client";
import { useState } from "react";
interface FormData {
name: string;
email: string;
subject: string;
message: string;
}
export default function ContactForm() {
const [formData, setFormData] = useState<FormData>({
name: "",
email: "",
subject: "",
message: "",
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [errors, setErrors] = useState<Partial<FormData>>({});
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
\/\/ Clear error when user starts typing
if (errors[name as keyof FormData]) {
setErrors((prev) => ({
...prev,
[name]: "",
}));
}
};
const validateForm = (): boolean => {
const newErrors: Partial<FormData> = {};
if (!formData.name.trim()) {
newErrors.name = "Name is required";
}
if (!formData.email.trim()) {
newErrors.email = "Email is required";
} else if (!\/\\S+@\\S+\\.\\S+\/.test(formData.email)) {
newErrors.email = "Email is invalid";
}
if (!formData.subject.trim()) {
newErrors.subject = "Subject is required";
}
if (!formData.message.trim()) {
newErrors.message = "Message is required";
} else if (formData.message.length < 10) {
newErrors.message = "Message must be at least 10 characters";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) {
return;
}
setIsSubmitting(true);
\/\/ Simulate API call
try {
await new Promise((resolve) => setTimeout(resolve, 2000));
setIsSubmitted(true);
setFormData({
name: "",
email: "",
subject: "",
message: "",
});
} catch (error) {
console.error("Error submitting form:", error);
} finally {
setIsSubmitting(false);
}
};
if (isSubmitted) {
return (
<div className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-2xl mx-auto">
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-8 text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
<svg
className="w-8 h-8 text-green-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
\/>
<\/svg>
<\/div>
<h2 className="text-3xl font-bold text-gray-900 mb-4">
Message Sent Successfully!
<\/h2>
<p className="text-lg text-gray-600 mb-8">
Thank you for reaching out. We'll get back to you within 24
hours.
<\/p>
<button
onClick={() => setIsSubmitted(false)}
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-medium transition-colors"
>
Send Another Message
<\/button>
<\/div>
<\/div>
<\/div>
);
}
return (
<div className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-2xl mx-auto">
{\/* Header *\/}
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-4">Contact Us<\/h1>
<p className="text-xl text-gray-600">
We'd love to hear from you. Send us a message and we'll
respond as soon as possible.
<\/p>
<\/div>
{\/* Contact Form *\/}
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-8">
<form onSubmit={handleSubmit} className="space-y-6">
{\/* Name & Email Row *\/}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-700 mb-2"
>
Full Name *
<\/label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleInputChange}
className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors ${
errors.name ? "border-red-500" : "border-gray-300"
}`}
placeholder="Enter your full name"
\/>
{errors.name && (
<p className="mt-1 text-sm text-red-600">{errors.name}<\/p>
)}
<\/div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-2"
>
Email Address *
<\/label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleInputChange}
className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors ${
errors.email ? "border-red-500" : "border-gray-300"
}`}
placeholder="Enter your email"
\/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email}<\/p>
)}
<\/div>
<\/div>
{\/* Subject *\/}
<div>
<label
htmlFor="subject"
className="block text-sm font-medium text-gray-700 mb-2"
>
Subject *
<\/label>
<select
id="subject"
name="subject"
value={formData.subject}
onChange={handleInputChange}
className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors ${
errors.subject ? "border-red-500" : "border-gray-300"
}`}
>
<option value="">Select a subject<\/option>
<option value="general">General Inquiry<\/option>
<option value="support">Technical Support<\/option>
<option value="billing">Billing Question<\/option>
<option value="partnership">Partnership<\/option>
<option value="feedback">Feedback<\/option>
<option value="other">Other<\/option>
<\/select>
{errors.subject && (
<p className="mt-1 text-sm text-red-600">{errors.subject}<\/p>
)}
<\/div>
{\/* Message *\/}
<div>
<label
htmlFor="message"
className="block text-sm font-medium text-gray-700 mb-2"
>
Message *
<\/label>
<textarea
id="message"
name="message"
rows={5}
value={formData.message}
onChange={handleInputChange}
className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors resize-none ${
errors.message ? "border-red-500" : "border-gray-300"
}`}
placeholder="Tell us more about your inquiry..."
\/>
{errors.message && (
<p className="mt-1 text-sm text-red-600">{errors.message}<\/p>
)}
<p className="mt-1 text-sm text-gray-500">
{formData.message.length}\/500 characters
<\/p>
<\/div>
{\/* Submit Button *\/}
<button
type="submit"
disabled={isSubmitting}
className={`w-full py-3 px-6 rounded-lg font-medium transition-colors ${
isSubmitting
? "bg-blue-400 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700"
} text-white`}
>
{isSubmitting ? (
<div className="flex items-center justify-center gap-2">
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" \/>
Sending Message...
<\/div>
) : (
"Send Message"
)}
<\/button>
<\/form>
<\/div>
{\/* Contact Info *\/}
<div className="mt-12 grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="text-center">
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mx-auto mb-4">
<svg
className="w-6 h-6 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
\/>
<\/svg>
<\/div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Email<\/h3>
<p className="text-gray-600">hello@company.com<\/p>
<\/div>
<div className="text-center">
<div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mx-auto mb-4">
<svg
className="w-6 h-6 text-green-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
\/>
<\/svg>
<\/div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Phone<\/h3>
<p className="text-gray-600">+1 (555) 123-4567<\/p>
<\/div>
<div className="text-center">
<div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mx-auto mb-4">
<svg
className="w-6 h-6 text-purple-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
\/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
\/>
<\/svg>
<\/div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Office<\/h3>
<p className="text-gray-600">San Francisco, CA<\/p>
<\/div>
<\/div>
<\/div>
<\/div>
);
}
How to use
- 1. Copy the component code
- 2. Paste it into your React/Next.js project
- 3. Make sure you have Tailwind CSS configured
- 4. Customize colors, spacing, and content as needed
Dependencies
react
^18.0.0tailwindcss
^3.0.0Back to Components
Need complete templates?
View Premium Templates