Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> CSS

Cách tạo form liên hệ (contact form) với CSS

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

  1. HTML5: required, type="email", minlength.
  2. CSS: :invalid pseudo-class (style đỏ khi invalid).
  3. JS: Kiểm tra real-time (on input), submit (preventDefault + validate all).
  4. 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.