Giới thiệu
Contact form (form liên hệ) là cầu nối giữa người dùng và chủ website. Cần: dễ tìm, ít trường bắt buộc, validation rõ ràng, feedback sau khi gửi, và bảo vệ spam (honeypot, reCAPTCHA).
Ví dụ hoàn chỉnh - Contact Form chuyên nghiệp
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Form</title>
<style>
* { box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f8f9fa; padding: 40px 20px; }
.contact-wrapper { max-width: 600px; margin: 0 auto; }
.contact-card {
background: #fff;
border-radius: 16px;
box-shadow: 0 4px 24px rgba(0,0,0,0.06);
padding: 40px;
border: 1px solid #eef0f2;
}
.contact-header { text-align: center; margin-bottom: 32px; }
.contact-header h1 { margin: 0 0 8px; font-size: 28px; color: #1a1a1a; }
.contact-header p { margin: 0; color: #666; font-size: 15px; }
.form-row { display: flex; gap: 20px; margin-bottom: 20px; }
.form-group { flex: 1; min-width: 0; }
.form-group.full { flex-basis: 100%; }
label { display: block; margin-bottom: 8px; font-weight: 500; font-size: 14px; color: #333; }
input[type="text"], input[type="email"], input[type="tel"], textarea {
width: 100%;
padding: 14px 16px;
font-size: 15px;
font-family: inherit;
border: 1.5px solid #e0e0e0;
border-radius: 10px;
background: #fff;
transition: all 0.2s;
}
textarea { min-height: 140px; resize: vertical; }
input:focus, textarea:focus { outline: none; border-color: #1eadef; box-shadow: 0 0 0 3px rgba(30,173,239,0.15); }
input::placeholder, textarea::placeholder { color: #aaa; }
.error-message { display: none; color: #e53935; font-size: 13px; margin-top: 6px; }
.form-group.has-error input, .form-group.has-error textarea { border-color: #e53935; }
.form-group.has-error .error-message { display: block; }
.btn-submit {
width: 100%;
padding: 16px;
font-size: 16px;
font-weight: 600;
color: #fff;
background: linear-gradient(135deg, #1eadef, #512c94);
border: none;
border-radius: 10px;
cursor: pointer;
transition: transform 0.1s, box-shadow 0.2s;
margin-top: 8px;
}
.btn-submit:hover { box-shadow: 0 6px 20px rgba(30,173,239,0.3); }
.btn-submit:active { transform: scale(0.98); }
.btn-submit:disabled { opacity: 0.7; cursor: not-allowed; }
.form-status { text-align: center; margin-top: 20px; padding: 16px; border-radius: 8px; display: none; }
.form-status.success { display: block; background: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; }
.form-status.error { display: block; background: #fdeaea; color: #c62828; border: 1px solid #f5c6cb; }
@media (max-width: 600px) {
.form-row { flex-direction: column; gap: 0; }
.contact-card { padding: 24px; }
}
</style>
</head>
<body>
<div class="contact-wrapper">
<div class="contact-card">
<div class="contact-header">
<h1>Liên hệ với chúng tôi</h1>
<p>Điền form bên dưới, chúng tôi sẽ phản hồi trong 24h.</p>
</div>
<form id="contactForm" novalidate>
<div class="form-row">
<div class="form-group">
<label for="firstName">Họ</label>
<input type="text" id="firstName" name="firstName" placeholder="Nguyễn" required autocomplete="given-name">
<span class="error-message">Vui lòng nhập họ</span>
</div>
<div class="form-group">
<label for="lastName">Tên</label>
<input type="text" id="lastName" name="lastName" placeholder="Văn An" required autocomplete="family-name">
<span class="error-message">Vui lòng nhập tên</span>
</div>
</div>
<div class="form-group full">
<label for="email">Email</label>
<input type="email" id="email" name="email" placeholder="email@example.com" required autocomplete="email">
<span class="error-message">Email không hợp lệ</span>
</div>
<div class="form-group full">
<label for="phone">Số điện thoại (tùy chọn)</label>
<input type="tel" id="phone" name="phone" placeholder="09x xxx xxxx" autocomplete="tel">
</div>
<div class="form-group full">
<label for="message">Nội dung</label>
<textarea id="message" name="message" placeholder="Mô tả câu hỏi, ý kiến hoặc yêu cầu của bạn..." required></textarea>
<span class="error-message">Vui lòng nhập nội dung</span>
</div>
<!-- Honeypot anti-spam -->
<input type="checkbox" id="hp" name="hp" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit" class="btn-submit">Gửi tin nhắn</button>
</form>
<div id="formStatus" class="form-status"></div>
</div>
</div>
<script>
const form = document.getElementById('contactForm');
const status = document.getElementById('formStatus');
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Honeypot check
if (form.hp.checked) return; // Bot filled hidden field
// Client-side validation
let valid = true;
form.querySelectorAll('[required]').forEach(input => {
const group = input.closest('.form-group');
if (!input.value.trim() || (input.type === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.value))) {
group.classList.add('has-error');
valid = false;
} else { group.classList.remove('has-error'); }
});
if (!valid) return;
// Simulate submit
const btn = form.querySelector('.btn-submit');
btn.disabled = true; btn.textContent = 'Đang gửi...';
await new Promise(r => setTimeout(r, 1500)); // Giả lập API
// Success
form.reset();
btn.disabled = false; btn.textContent = 'Gửi tin nhắn';
status.className = 'form-status success';
status.textContent = 'Cảm ơn bạn! Tin nhắn đã được gửi thành công.';
});
// Clear error on input
form.querySelectorAll('input, textarea').forEach(input => {
input.addEventListener('input', () => input.closest('.form-group').classList.remove('has-error'));
});
</script>
</body>
</html>
Các thành phần bắt buộc
- Họ/Tên riêng biệt: Dễ cá nhân hóa email trả lời.
- Email (bắt buộc):
type="email"+ validation regex. - Số điện thoại (tùy chọn):
type="tel". - Textarea message:
min-height: 140px; resize: vertical. - Honeypot field:
<input type="checkbox" style="display:none">- bot tự tick, request bị từ chối.
Validation Strategy
- HTML5:
required,type="email",minlength. - CSS:
:invalidpseudo-class (style đỏ khi invalid). - JS: Kiểm tra real-time (on input), submit (preventDefault + validate all).
- Server-side: Luôn validate lại phía server (bắt buộc).
Feedback sau submit
- Loading state: disable button, text "Đang gửi...".
- Success: Thông báo xanh, reset form.
- Error: Thông báo đỏ, giữ dữ liệu form.