Labs ICT

Build a Form Page

Build a Form Page Project

In this project, you'll learn how to create a comprehensive contact form with validation, proper structure, and user-friendly features. Forms are essential for collecting user information on websites.

Project Overview

We'll build a professional contact form with:

  • Multiple input types and validation
  • Real-time validation feedback
  • Responsive design
  • Accessibility features
  • Form submission handling
  • Error and success states

Step 1: Basic HTML Structure

Start with the semantic HTML structure for your form:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Contact Form Project</title>
</head>
<body>
  <main>
    <section class="form-container">
      <h1>Contact Us</h1>
      <p>We'd love to hear from you. Send us a message and we'll respond as soon as possible.</p>
      
      <form id="contactForm" method="post" action="/submit">
        <div class="form-group">
          <label for="name">Full Name</label>
          <input type="text" id="name" name="name" required>
        </div>
        
        <div class="form-group">
          <label for="email">Email Address</label>
          <input type="email" id="email" name="email" required>
        </div>
        
        <div class="form-group">
          <label for="message">Message</label>
          <textarea id="message" name="message" rows="5" required></textarea>
        </div>
        
        <button type="submit">Send Message</button>
      </form>
    </section>
  </main>
</body>
</html>

Step 2: Basic CSS Styling

Add CSS to create a clean, professional form design:

/* Basic reset and typography */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Arial', sans-serif;
  line-height: 1.6;
  color: #333;
  background-color: #f4f4f4;
}

/* Form container */
.form-container {
  max-width: 600px;
  margin: 2rem auto;
  padding: 2rem;
  background: white;
  border-radius: 10px;
  box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}

.form-container h1 {
  text-align: center;
  margin-bottom: 1rem;
  color: #2c3e50;
}

.form-container p {
  text-align: center;
  margin-bottom: 2rem;
  color: #666;
}

/* Form styling */
form {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.form-group {
  display: flex;
  flex-direction: column;
}

label {
  margin-bottom: 0.5rem;
  font-weight: 600;
  color: #2c3e50;
}

input, textarea {
  padding: 0.75rem;
  border: 2px solid #ddd;
  border-radius: 5px;
  font-size: 1rem;
  transition: border-color 0.3s;
}

input:focus, textarea:focus {
  outline: none;
  border-color: #3498db;
}

textarea {
  resize: vertical;
  min-height: 120px;
}

button {
  background-color: #3498db;
  color: white;
  padding: 1rem 2rem;
  border: none;
  border-radius: 5px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.3s;
}

button:hover {
  background-color: #2980b9;
}

Step 3: Add More Input Types

Expand your form with various input types for different data:

<form id="contactForm" method="post" action="/submit">
  <div class="form-row">
    <div class="form-group">
      <label for="firstName">First Name</label>
      <input type="text" id="firstName" name="firstName" required>
    </div>
    
    <div class="form-group">
      <label for="lastName">Last Name</label>
      <input type="text" id="lastName" name="lastName" required>
    </div>
  </div>
  
  <div class="form-group">
    <label for="email">Email Address</label>
    <input type="email" id="email" name="email" required>
  </div>
  
  <div class="form-group">
    <label for="phone">Phone Number</label>
    <input type="tel" id="phone" name="phone" placeholder="(123) 456-7890">
  </div>
  
  <div class="form-group">
    <label for="subject">Subject</label>
    <select id="subject" name="subject" required>
      <option value="">Select a subject</option>
      <option value="general">General Inquiry</option>
      <option value="support">Technical Support</option>
      <option value="sales">Sales Question</option>
      <option value="feedback">Feedback</option>
    </select>
  </div>
  
  <div class="form-group">
    <label for="priority">Priority</label>
    <div class="radio-group">
      <label><input type="radio" name="priority" value="low" checked> Low</label>
      <label><input type="radio" name="priority" value="medium"> Medium</label>
      <label><input type="radio" name="priority" value="high"> High</label>
    </div>
  </div>
  
  <div class="form-group">
    <label for="message">Message</label>
    <textarea id="message" name="message" rows="5" required></textarea>
  </div>
  
  <div class="form-group">
    <div class="checkbox-group">
      <label><input type="checkbox" name="newsletter" id="newsletter"> Subscribe to newsletter</label>
      <label><input type="checkbox" name="terms" id="terms" required> I agree to the terms and conditions</label>
    </div>
  </div>
  
  <div class="form-actions">
    <button type="submit">Send Message</button>
    <button type="reset">Clear Form</button>
  </div>
</form>
/* Form rows for side-by-side fields */
.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

/* Select styling */
select {
  padding: 0.75rem;
  border: 2px solid #ddd;
  border-radius: 5px;
  font-size: 1rem;
  background-color: white;
  cursor: pointer;
}

/* Radio and checkbox groups */
.radio-group, .checkbox-group {
  display: flex;
  gap: 1.5rem;
  margin-top: 0.5rem;
}

.radio-group label, .checkbox-group label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: normal;
  cursor: pointer;
}

input[type="radio"], input[type="checkbox"] {
  width: auto;
  margin: 0;
}

/* Form actions */
.form-actions {
  display: flex;
  gap: 1rem;
  justify-content: center;
  margin-top: 1rem;
}

button[type="reset"] {
  background-color: #95a5a6;
}

button[type="reset"]:hover {
  background-color: #7f8c8d;
}

Step 4: Add Form Validation

Implement client-side validation with HTML5 attributes and JavaScript:

<form id="contactForm" method="post" action="/submit" novalidate>
  <div class="form-group">
    <label for="firstName">First Name</label>
    <input type="text" 
           id="firstName" 
           name="firstName" 
           required 
           minlength="2" 
           maxlength="50"
           pattern="[A-Za-z\s]+"
           title="Only letters and spaces allowed">
    <span class="error-message"></span>
  </div>
  
  <div class="form-group">
    <label for="email">Email Address</label>
    <input type="email" 
           id="email" 
           name="email" 
           required
           title="Please enter a valid email address">
    <span class="error-message"></span>
  </div>
  
  <div class="form-group">
    <label for="phone">Phone Number</label>
    <input type="tel" 
           id="phone" 
           name="phone" 
           placeholder="(123) 456-7890"
           pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
           title="Format: 123-456-7890">
    <span class="error-message"></span>
  </div>
  
  <div class="form-group">
    <label for="message">Message</label>
    <textarea id="message" 
              name="message" 
              rows="5" 
              required 
              minlength="10"
              maxlength="500"></textarea>
    <span class="error-message"></span>
  </div>
  
  <button type="submit">Send Message</button>
</form>
/* Error styling */
.error-message {
  color: #e74c3c;
  font-size: 0.875rem;
  margin-top: 0.25rem;
  display: block;
}

input:invalid, textarea:invalid {
  border-color: #e74c3c;
}

input:valid, textarea:valid {
  border-color: #27ae60;
}

Step 5: Add JavaScript Validation

Enhance validation with real-time feedback and custom error messages:

// Form validation
document.addEventListener('DOMContentLoaded', function() {
  const form = document.getElementById('contactForm');
  const inputs = form.querySelectorAll('input, textarea');
  
  // Real-time validation
  inputs.forEach(input => {
    input.addEventListener('blur', function() {
      validateField(this);
    });
    
    input.addEventListener('input', function() {
      clearError(this);
    });
  });
  
  // Form submission
  form.addEventListener('submit', function(e) {
    e.preventDefault();
    
    if (validateForm()) {
      submitForm();
    }
  });
});

function validateField(field) {
  const errorMessage = field.nextElementSibling;
  let isValid = true;
  let message = '';
  
  // Clear previous error
  clearError(field);
  
  // Check if field is required and empty
  if (field.hasAttribute('required') && !field.value.trim()) {
    isValid = false;
    message = 'This field is required';
  }
  
  // Email validation
  if (field.type === 'email' && field.value) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(field.value)) {
      isValid = false;
      message = 'Please enter a valid email address';
    }
  }
  
  // Phone validation
  if (field.type === 'tel' && field.value) {
    const phoneRegex = /^\d{3}-\d{3}-\d{4}$/;
    if (!phoneRegex.test(field.value)) {
      isValid = false;
      message = 'Format: 123-456-7890';
    }
  }
  
  // Min length validation
  if (field.hasAttribute('minlength') && field.value.length < parseInt(field.getAttribute('minlength'))) {
    isValid = false;
    message = `Minimum ${field.getAttribute('minlength')} characters required`;
  }
  
  // Pattern validation
  if (field.hasAttribute('pattern') && field.value) {
    const pattern = new RegExp(field.getAttribute('pattern'));
    if (!pattern.test(field.value)) {
      isValid = false;
      message = field.getAttribute('title') || 'Invalid format';
    }
  }
  
  // Show error or success
  if (!isValid) {
    showError(field, message);
  }
  
  return isValid;
}

function showError(field, message) {
  const errorMessage = field.nextElementSibling;
  field.classList.add('error');
  if (errorMessage && errorMessage.classList.contains('error-message')) {
    errorMessage.textContent = message;
  }
}

function clearError(field) {
  field.classList.remove('error');
  const errorMessage = field.nextElementSibling;
  if (errorMessage && errorMessage.classList.contains('error-message')) {
    errorMessage.textContent = '';
  }
}

function validateForm() {
  const form = document.getElementById('contactForm');
  const inputs = form.querySelectorAll('input[required], textarea[required]');
  let isValid = true;
  
  inputs.forEach(input => {
    if (!validateField(input)) {
      isValid = false;
    }
  });
  
  return isValid;
}

function submitForm() {
  const form = document.getElementById('contactForm');
  const formData = new FormData(form);
  const data = Object.fromEntries(formData);
  
  // Show success message
  showSuccessMessage();
  
  // Log form data (in production, send to server)
  console.log('Form submitted:', data);
  
  // Reset form
  form.reset();
}

function showSuccessMessage() {
  const successDiv = document.createElement('div');
  successDiv.className = 'success-message';
  successDiv.textContent = 'Thank you! Your message has been sent successfully.';
  
  const form = document.getElementById('contactForm');
  form.parentNode.insertBefore(successDiv, form);
  
  // Remove message after 5 seconds
  setTimeout(() => {
    successDiv.remove();
  }, 5000);
}
/* JavaScript validation styling */
input.error, textarea.error {
  border-color: #e74c3c;
  background-color: #fdf2f2;
}

.success-message {
  background-color: #d4edda;
  color: #155724;
  padding: 1rem;
  border-radius: 5px;
  margin-bottom: 1rem;
  text-align: center;
  border: 1px solid #c3e6cb;
}

Step 6: Add Responsive Design

Make your form work perfectly on all devices:

/* Responsive design */
@media (max-width: 768px) {
  .form-container {
    margin: 1rem;
    padding: 1.5rem;
  }
  
  .form-row {
    grid-template-columns: 1fr;
  }
  
  .radio-group, .checkbox-group {
    flex-direction: column;
    gap: 0.75rem;
  }
  
  .form-actions {
    flex-direction: column;
  }
  
  .form-actions button {
    width: 100%;
  }
}

@media (max-width: 480px) {
  .form-container {
    margin: 0.5rem;
    padding: 1rem;
  }
  
  .form-container h1 {
    font-size: 1.5rem;
  }
  
  input, textarea, select {
    padding: 0.6rem;
  }
  
  button {
    padding: 0.8rem 1.5rem;
  }
}

Step 7: Add Accessibility Features

Make your form accessible to all users:

<form id="contactForm" method="post" action="/submit" novalidate>
  <fieldset>
    <legend>Personal Information</legend>
    
    <div class="form-row">
      <div class="form-group">
        <label for="firstName">First Name<span aria-label="required">*</span></label>
        <input type="text" 
               id="firstName" 
               name="firstName" 
               required 
               aria-required="true"
               aria-describedby="firstName-error"
               minlength="2" 
               maxlength="50">
        <span id="firstName-error" class="error-message" role="alert"></span>
      </div>
      
      <div class="form-group">
        <label for="lastName">Last Name<span aria-label="required">*</span></label>
        <input type="text" 
               id="lastName" 
               name="lastName" 
               required 
               aria-required="true"
               aria-describedby="lastName-error"
               minlength="2" 
               maxlength="50">
        <span id="lastName-error" class="error-message" role="alert"></span>
      </div>
    </div>
  </fieldset>
  
  <fieldset>
    <legend>Contact Details</legend>
    
    <div class="form-group">
      <label for="email">Email Address<span aria-label="required">*</span></label>
      <input type="email" 
             id="email" 
             name="email" 
             required 
             aria-required="true"
             aria-describedby="email-error"
             aria-label="Email address in format: user@example.com">
      <span id="email-error" class="error-message" role="alert"></span>
    </div>
  </fieldset>
  
  <div class="form-actions">
    <button type="submit" aria-label="Submit contact form">Send Message</button>
    <button type="reset" aria-label="Clear all form fields">Clear Form</button>
  </div>
</form>
/* Accessibility improvements */
fieldset {
  border: 1px solid #ddd;
  border-radius: 5px;
  padding: 1.5rem;
  margin-bottom: 1.5rem;
}

legend {
  font-weight: 600;
  color: #2c3e50;
  padding: 0 0.5rem;
}

/* Focus indicators */
input:focus, textarea:focus, select:focus, button:focus {
  outline: 3px solid #3498db;
  outline-offset: 2px;
}

/* High contrast mode support */
@media (prefers-contrast: high) {
  input, textarea, select {
    border: 2px solid #000;
  }
  
  button {
    border: 2px solid #000;
  }
}

/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
  input, textarea, select, button {
    transition: none;
  }
}

Step 8: Add Advanced Features

Enhance your form with advanced functionality:

// Advanced form features
document.addEventListener('DOMContentLoaded', function() {
  // Auto-save form data
  const form = document.getElementById('contactForm');
  const inputs = form.querySelectorAll('input, textarea, select');
  
  // Load saved data
  loadFormData();
  
  // Auto-save on input
  inputs.forEach(input => {
    input.addEventListener('input', debounce(saveFormData, 500));
  });
  
  // Character counter
  const messageField = document.getElementById('message');
  const counter = createCharacterCounter(messageField);
  messageField.parentNode.appendChild(counter);
  
  // Form progress indicator
  const progressIndicator = createProgressIndicator();
  form.insertBefore(progressIndicator, form.firstChild);
  
  updateProgress();
});

function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

function saveFormData() {
  const form = document.getElementById('contactForm');
  const formData = new FormData(form);
  const data = Object.fromEntries(formData);
  
  localStorage.setItem('formData', JSON.stringify(data));
}

function loadFormData() {
  const savedData = localStorage.getItem('formData');
  if (savedData) {
    const data = JSON.parse(savedData);
    const form = document.getElementById('contactForm');
    
    Object.keys(data).forEach(key => {
      const field = form.querySelector(`[name="${key}"]`);
      if (field) {
        if (field.type === 'checkbox' || field.type === 'radio') {
          field.checked = data[key];
        } else {
          field.value = data[key];
        }
      }
    });
  }
}

function createCharacterCounter(textarea) {
  const counter = document.createElement('div');
  counter.className = 'character-counter';
  counter.textContent = `0 / ${textarea.getAttribute('maxlength') || 500}`;
  
  textarea.addEventListener('input', function() {
    const current = this.value.length;
    const max = this.getAttribute('maxlength') || 500;
    counter.textContent = `${current} / ${max}`;
    
    if (current > max * 0.9) {
      counter.classList.add('warning');
    } else {
      counter.classList.remove('warning');
    }
  });
  
  return counter;
}

function createProgressIndicator() {
  const progress = document.createElement('div');
  progress.className = 'form-progress';
  progress.innerHTML = `
    <div class="progress-bar">
      <div class="progress-fill"></div>
    </div>
    <span class="progress-text">Form Progress: 0%</span>
  `;
  return progress;
}

function updateProgress() {
  const form = document.getElementById('contactForm');
  const requiredFields = form.querySelectorAll('[required]');
  const filledFields = Array.from(requiredFields).filter(field => field.value.trim());
  const progress = (filledFields.length / requiredFields.length) * 100;
  
  const progressFill = document.querySelector('.progress-fill');
  const progressText = document.querySelector('.progress-text');
  
  if (progressFill && progressText) {
    progressFill.style.width = `${progress}%`;
    progressText.textContent = `Form Progress: ${Math.round(progress)}%`;
  }
}

// Update progress on input
document.addEventListener('input', updateProgress);
document.addEventListener('change', updateProgress);
/* Advanced features styling */
.character-counter {
  font-size: 0.875rem;
  color: #666;
  text-align: right;
  margin-top: 0.25rem;
}

.character-counter.warning {
  color: #e74c3c;
  font-weight: 600;
}

.form-progress {
  margin-bottom: 2rem;
  padding: 1rem;
  background-color: #f8f9fa;
  border-radius: 5px;
}

.progress-bar {
  width: 100%;
  height: 8px;
  background-color: #e9ecef;
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 0.5rem;
}

.progress-fill {
  height: 100%;
  background-color: #3498db;
  transition: width 0.3s ease;
}

.progress-text {
  font-size: 0.875rem;
  color: #666;
  font-weight: 500;
}

Complete Form Project Code

Here's the complete, production-ready contact form:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Contact Form - Professional Form Project</title>
  <style>
    /* All CSS from previous steps goes here */
  </style>
</head>
<body>
  <main>
    <section class="form-container">
      <h1>Contact Us</h1>
      <p>We'd love to hear from you. Send us a message and we'll respond as soon as possible.</p>
      
      <form id="contactForm" method="post" action="/submit" novalidate>
        <div class="form-progress">
          <div class="progress-bar">
            <div class="progress-fill"></div>
          </div>
          <span class="progress-text">Form Progress: 0%</span>
        </div>
        
        <fieldset>
          <legend>Personal Information</legend>
          
          <div class="form-row">
            <div class="form-group">
              <label for="firstName">First Name<span aria-label="required">*</span></label>
              <input type="text" 
                     id="firstName" 
                     name="firstName" 
                     required 
                     aria-required="true"
                     aria-describedby="firstName-error"
                     minlength="2" 
                     maxlength="50">
              <span id="firstName-error" class="error-message" role="alert"></span>
            </div>
            
            <div class="form-group">
              <label for="lastName">Last Name<span aria-label="required">*</span></label>
              <input type="text" 
                     id="lastName" 
                     name="lastName" 
                     required 
                     aria-required="true"
                     aria-describedby="lastName-error"
                     minlength="2" 
                     maxlength="50">
              <span id="lastName-error" class="error-message" role="alert"></span>
            </div>
          </div>
        </fieldset>
        
        <fieldset>
          <legend>Contact Details</legend>
          
          <div class="form-group">
            <label for="email">Email Address<span aria-label="required">*</span></label>
            <input type="email" 
                   id="email" 
                   name="email" 
                   required 
                   aria-required="true"
                   aria-describedby="email-error">
            <span id="email-error" class="error-message" role="alert"></span>
          </div>
          
          <div class="form-group">
            <label for="phone">Phone Number</label>
            <input type="tel" 
                   id="phone" 
                   name="phone" 
                   placeholder="(123) 456-7890"
                   pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
                   aria-describedby="phone-error">
            <span id="phone-error" class="error-message" role="alert"></span>
          </div>
        </fieldset>
        
        <fieldset>
          <legend>Message Details</legend>
          
          <div class="form-group">
            <label for="subject">Subject<span aria-label="required">*</span></label>
            <select id="subject" name="subject" required aria-required="true">
              <option value="">Select a subject</option>
              <option value="general">General Inquiry</option>
              <option value="support">Technical Support</option>
              <option value="sales">Sales Question</option>
              <option value="feedback">Feedback</option>
            </select>
          </div>
          
          <div class="form-group">
            <label for="message">Message<span aria-label="required">*</span></label>
            <textarea id="message" 
                      name="message" 
                      rows="5" 
                      required 
                      aria-required="true"
                      aria-describedby="message-error"
                      minlength="10"
                      maxlength="500"></textarea>
            <span id="message-error" class="error-message" role="alert"></span>
          </div>
        </fieldset>
        
        <div class="form-group">
          <div class="checkbox-group">
            <label><input type="checkbox" name="newsletter" id="newsletter"> Subscribe to newsletter</label>
            <label><input type="checkbox" name="terms" id="terms" required> I agree to the terms and conditions</label>
          </div>
        </div>
        
        <div class="form-actions">
          <button type="submit" aria-label="Submit contact form">Send Message</button>
          <button type="reset" aria-label="Clear all form fields">Clear Form</button>
        </div>
      </form>
    </section>
  </main>
  
  <script>
    // All JavaScript from previous steps goes here
  </script>
</body>
</html>

Project Summary

Congratulations! You've built a professional contact form with:

  • Multiple input types and validation
  • Real-time validation feedback
  • Responsive design for all devices
  • Accessibility features and ARIA attributes
  • Auto-save functionality
  • Progress indicator
  • Character counter
  • Error handling and success messages

Key Form Principles:

  • Always validate on both client and server side
  • Provide clear error messages and feedback
  • Use semantic HTML for accessibility
  • Make forms responsive and mobile-friendly
  • Include proper ARIA attributes
  • Test with keyboard navigation
  • Provide visual and programmatic feedback

Next Steps:

  • Add server-side validation and processing
  • Implement file upload functionality
  • Add multi-step form wizard
  • Create form analytics and tracking
  • Add CAPTCHA for spam protection

๐Ÿงช Quick Quiz

Which HTML element creates a dropdown list?