User Registration Form Tutorial: HTML, React & Vue (2026)

16 min read
Static Forms Team

Creating a user registration form is one of the most common requirements for web applications. Whether you're building a SaaS platform, membership site, online community, or any application requiring user accounts, a well-designed registration form is your first touchpoint with new users.

In this comprehensive tutorial, you'll learn how to build secure, user-friendly registration forms with email verification, password validation, and GDPR compliance—all without managing backend infrastructure.

What You'll Build

By the end of this tutorial, you'll have production-ready registration forms with:

  • ✅ Email and password validation
  • ✅ Password strength indicators
  • ✅ Email verification workflow
  • ✅ Terms & conditions agreement
  • ✅ GDPR consent management
  • ✅ Secure data handling
  • ✅ Examples in HTML, React, and Vue.js
  • ✅ Spam protection built-in

Why User Registration Forms Matter

User registration forms serve as the gateway to your application. A well-designed registration form:

Increases Conversion Rates

  • Simplified forms reduce friction
  • Clear error messages prevent abandonment
  • Progressive disclosure keeps users engaged

Ensures Data Quality

  • Validation prevents fake or incorrect data
  • Email verification confirms real users
  • Strong passwords protect user accounts

Builds Trust

  • GDPR compliance shows respect for privacy
  • Security measures demonstrate professionalism
  • Transparent data usage builds confidence

Prevents Fraud

  • Email verification reduces fake accounts
  • CAPTCHA protection blocks bots
  • Rate limiting prevents abuse

Essential Registration Form Fields

A well-designed registration form balances security with usability. Here are the essential fields:

Required Fields

Email Address

  • Primary identifier for the account
  • Used for login and communication
  • Must be validated and verified

Password

  • Minimum 8-12 characters
  • Mix of uppercase, lowercase, numbers, symbols
  • Password strength indicator helps users

Terms & Conditions Checkbox

  • Legal requirement in most jurisdictions
  • Must be explicitly checked (not pre-checked)
  • Link to full terms and privacy policy

Full Name or Username

  • Personalizes the user experience
  • Helps identify accounts
  • Can be collected during registration or onboarding

Password Confirmation

  • Prevents typos in passwords
  • Improves user experience
  • Reduces support requests

Newsletter/Marketing Consent

  • Separate from terms & conditions
  • Must be optional (GDPR requirement)
  • Clear opt-in language

HTML Registration Form Example

Let's start with a complete HTML registration form with validation:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>User Registration Form</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 20px;
    }

    .form-container {
      background: white;
      border-radius: 16px;
      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
      padding: 40px;
      max-width: 500px;
      width: 100%;
    }

    .form-header {
      text-center;
      margin-bottom: 30px;
    }

    .form-header h1 {
      color: #333;
      font-size: 28px;
      margin-bottom: 8px;
    }

    .form-header p {
      color: #666;
      font-size: 15px;
    }

    .form-group {
      margin-bottom: 24px;
    }

    label {
      display: block;
      font-weight: 600;
      color: #333;
      margin-bottom: 8px;
      font-size: 14px;
    }

    .required {
      color: #e74c3c;
      margin-left: 4px;
    }

    input[type="text"],
    input[type="email"],
    input[type="password"] {
      width: 100%;
      padding: 12px 16px;
      border: 2px solid #e1e8ed;
      border-radius: 8px;
      font-size: 15px;
      font-family: inherit;
      transition: border-color 0.3s, box-shadow 0.3s;
    }

    input:focus {
      outline: none;
      border-color: #667eea;
      box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
    }

    input.error {
      border-color: #e74c3c;
    }

    .password-strength {
      height: 4px;
      background: #e1e8ed;
      border-radius: 2px;
      margin-top: 8px;
      overflow: hidden;
    }

    .password-strength-bar {
      height: 100%;
      transition: width 0.3s, background-color 0.3s;
      width: 0;
    }

    .password-strength-bar.weak {
      width: 33%;
      background-color: #e74c3c;
    }

    .password-strength-bar.medium {
      width: 66%;
      background-color: #f39c12;
    }

    .password-strength-bar.strong {
      width: 100%;
      background-color: #27ae60;
    }

    .password-hint {
      font-size: 12px;
      color: #666;
      margin-top: 6px;
    }

    .error-message {
      color: #e74c3c;
      font-size: 13px;
      margin-top: 6px;
      display: none;
    }

    .error-message.show {
      display: block;
    }

    .checkbox-group {
      display: flex;
      align-items: flex-start;
      gap: 8px;
      margin-bottom: 20px;
    }

    .checkbox-group input[type="checkbox"] {
      margin-top: 4px;
      width: 18px;
      height: 18px;
      cursor: pointer;
    }

    .checkbox-group label {
      margin: 0;
      font-weight: 400;
      font-size: 14px;
      cursor: pointer;
      user-select: none;
    }

    .checkbox-group a {
      color: #667eea;
      text-decoration: none;
    }

    .checkbox-group a:hover {
      text-decoration: underline;
    }

    button[type="submit"] {
      width: 100%;
      padding: 14px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      border-radius: 8px;
      font-size: 16px;
      font-weight: 600;
      cursor: pointer;
      transition: transform 0.2s, box-shadow 0.2s;
    }

    button[type="submit"]:hover:not(:disabled) {
      transform: translateY(-2px);
      box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
    }

    button[type="submit"]:disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }

    .alert {
      padding: 16px;
      border-radius: 8px;
      margin-bottom: 24px;
      font-size: 14px;
    }

    .alert-success {
      background: #d4edda;
      border: 1px solid #c3e6cb;
      color: #155724;
    }

    .alert-error {
      background: #f8d7da;
      border: 1px solid #f5c6cb;
      color: #721c24;
    }

    .hidden {
      display: none !important;
    }

    .login-link {
      text-align: center;
      margin-top: 20px;
      color: #666;
      font-size: 14px;
    }

    .login-link a {
      color: #667eea;
      text-decoration: none;
      font-weight: 600;
    }

    .login-link a:hover {
      text-decoration: underline;
    }
  </style>
</head>
<body>
  <div class="form-container">
    <div class="form-header">
      <h1>Create Your Account</h1>
      <p>Join thousands of users already using our platform</p>
    </div>

    <div id="successMessage" class="alert alert-success hidden">
      ✅ Registration successful! Please check your email to verify your account.
    </div>

    <div id="errorMessage" class="alert alert-error hidden">
      ❌ Something went wrong. Please try again.
    </div>

    <form id="registrationForm">
      <div class="form-group">
        <label for="name">
          Full Name <span class="required">*</span>
        </label>
        <input
          type="text"
          id="name"
          name="name"
          required
          placeholder="John Doe"
        >
        <div class="error-message" id="nameError">Please enter your full name</div>
      </div>

      <div class="form-group">
        <label for="email">
          Email Address <span class="required">*</span>
        </label>
        <input
          type="email"
          id="email"
          name="email"
          required
          placeholder="john@example.com"
        >
        <div class="error-message" id="emailError">Please enter a valid email address</div>
      </div>

      <div class="form-group">
        <label for="password">
          Password <span class="required">*</span>
        </label>
        <input
          type="password"
          id="password"
          name="password"
          required
          placeholder="Create a strong password"
        >
        <div class="password-strength">
          <div class="password-strength-bar" id="strengthBar"></div>
        </div>
        <div class="password-hint">
          At least 8 characters with uppercase, lowercase, and numbers
        </div>
        <div class="error-message" id="passwordError">Password is too weak</div>
      </div>

      <div class="form-group">
        <label for="confirmPassword">
          Confirm Password <span class="required">*</span>
        </label>
        <input
          type="password"
          id="confirmPassword"
          name="confirmPassword"
          required
          placeholder="Re-enter your password"
        >
        <div class="error-message" id="confirmPasswordError">Passwords do not match</div>
      </div>

      <div class="checkbox-group">
        <input
          type="checkbox"
          id="terms"
          name="terms"
          required
        >
        <label for="terms">
          I agree to the <a href="/terms" target="_blank">Terms of Service</a> and <a href="/privacy" target="_blank">Privacy Policy</a> <span class="required">*</span>
        </label>
      </div>

      <div class="checkbox-group">
        <input
          type="checkbox"
          id="newsletter"
          name="newsletter"
        >
        <label for="newsletter">
          Send me product updates and occasional emails (optional)
        </label>
      </div>

      <button type="submit" id="submitBtn">
        Create Account
      </button>
    </form>

    <div class="login-link">
      Already have an account? <a href="/login">Sign in</a>
    </div>
  </div>

  <script>
    const form = document.getElementById('registrationForm');
    const passwordInput = document.getElementById('password');
    const confirmPasswordInput = document.getElementById('confirmPassword');
    const strengthBar = document.getElementById('strengthBar');
    const submitBtn = document.getElementById('submitBtn');
    const successMessage = document.getElementById('successMessage');
    const errorMessage = document.getElementById('errorMessage');

    // Password strength checker
    passwordInput.addEventListener('input', (e) => {
      const password = e.target.value;
      const strength = calculatePasswordStrength(password);

      strengthBar.className = 'password-strength-bar';

      if (strength === 0) {
        strengthBar.classList.remove('weak', 'medium', 'strong');
      } else if (strength <= 2) {
        strengthBar.classList.add('weak');
      } else if (strength <= 3) {
        strengthBar.classList.add('medium');
      } else {
        strengthBar.classList.add('strong');
      }
    });

    function calculatePasswordStrength(password) {
      let strength = 0;

      if (password.length >= 8) strength++;
      if (password.length >= 12) strength++;
      if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
      if (/[0-9]/.test(password)) strength++;
      if (/[^a-zA-Z0-9]/.test(password)) strength++;

      return strength;
    }

    // Form validation
    form.addEventListener('submit', async (e) => {
      e.preventDefault();

      // Hide previous messages
      successMessage.classList.add('hidden');
      errorMessage.classList.add('hidden');

      // Clear previous errors
      document.querySelectorAll('.error-message').forEach(el => el.classList.remove('show'));
      document.querySelectorAll('input').forEach(el => el.classList.remove('error'));

      // Validate
      let isValid = true;

      // Name validation
      const name = document.getElementById('name').value.trim();
      if (name.length < 2) {
        showError('name', 'nameError');
        isValid = false;
      }

      // Email validation
      const email = document.getElementById('email').value;
      if (!validateEmail(email)) {
        showError('email', 'emailError');
        isValid = false;
      }

      // Password validation
      const password = passwordInput.value;
      if (calculatePasswordStrength(password) < 2) {
        showError('password', 'passwordError');
        isValid = false;
      }

      // Confirm password validation
      const confirmPassword = confirmPasswordInput.value;
      if (password !== confirmPassword) {
        showError('confirmPassword', 'confirmPasswordError');
        isValid = false;
      }

      // Terms validation
      if (!document.getElementById('terms').checked) {
        alert('Please accept the Terms of Service and Privacy Policy');
        isValid = false;
      }

      if (!isValid) return;

      // Disable submit button
      submitBtn.disabled = true;
      submitBtn.textContent = 'Creating Account...';

      try {
        const formData = new FormData(form);

        // Add Static Forms fields
        formData.append('apiKey', 'YOUR_API_KEY_HERE'); // Replace with your API key
        formData.append('subject', 'New User Registration');
        formData.append('replyTo', email);

        const response = await fetch('https://api.staticforms.dev/submit', {
          method: 'POST',
          body: formData
        });

        const result = await response.json();

        if (result.success) {
          successMessage.classList.remove('hidden');
          form.reset();
          strengthBar.className = 'password-strength-bar';

          // Scroll to success message
          successMessage.scrollIntoView({ behavior: 'smooth', block: 'center' });
        } else {
          throw new Error('Registration failed');
        }
      } catch (error) {
        errorMessage.classList.remove('hidden');
        console.error('Error:', error);
      } finally {
        submitBtn.disabled = false;
        submitBtn.textContent = 'Create Account';
      }
    });

    function showError(inputId, errorId) {
      document.getElementById(inputId).classList.add('error');
      document.getElementById(errorId).classList.add('show');
    }

    function validateEmail(email) {
      const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      return re.test(email);
    }
  </script>
</body>
</html>

This complete HTML registration form includes:

  • Real-time password strength indicator
  • Form validation
  • Professional styling
  • Mobile-responsive design
  • Clear error messages

Adding Password Strength Validation

Strong password validation is critical for security. Here's a comprehensive validation function:

JavaScript
function validatePasswordStrength(password) {
  const requirements = {
    minLength: password.length >= 8,
    maxLength: password.length <= 128,
    hasUppercase: /[A-Z]/.test(password),
    hasLowercase: /[a-z]/.test(password),
    hasNumber: /[0-9]/.test(password),
    hasSpecial: /[!@#$%^&*(),.?":{}|<>]/.test(password),
  };

  const score = Object.values(requirements).filter(Boolean).length;

  return {
    isValid: requirements.minLength && requirements.hasUppercase &&
             requirements.hasLowercase && requirements.hasNumber,
    score,
    requirements,
    strength: score <= 2 ? 'weak' : score <= 4 ? 'medium' : 'strong',
  };
}

// Usage
const validation = validatePasswordStrength('MyP@ssw0rd');
console.log(validation.isValid); // true
console.log(validation.strength); // 'strong'

Best Practices for Password Validation

Minimum Requirements:

  • At least 8 characters (12+ recommended)
  • Mix of uppercase and lowercase letters
  • At least one number
  • At least one special character

Avoid:

  • Maximum length restrictions (allow up to 128 characters)
  • Overly complex rules that frustrate users
  • Rejecting valid special characters
  • Requiring frequent password changes

Show Users:

  • Real-time password strength feedback
  • Clear requirements before they start typing
  • Specific feedback on what's missing

Email Confirmation (Auto-Responder)

Static Forms can automatically send confirmation emails to users who register through your form. This provides immediate feedback and helps ensure they entered a valid email address.

To set up email confirmation:

  1. Go to your Static Forms dashboard
  2. Select your registration form
  3. Enable the "Auto-Responder" feature
  4. Customize your confirmation email template:
Plain Text
Subject: Registration Received

Hi {name},

Thank you for registering! We've received your information and will be in touch soon.

Registration Details:
- Email: {email}
- Submitted: {timestamp}

Best regards,
The [Your App] Team

Note: Static Forms auto-responder sends a simple confirmation email. If you need advanced email verification workflows (with unique tokens, verification links, or backend validation), you'll need to implement a custom backend solution outside of Static Forms.

React Registration Form Component

For React applications, here's a complete registration form with hooks:

JSX
import React, { useState, useEffect } from 'react';
import './RegistrationForm.css';

function RegistrationForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    password: '',
    confirmPassword: '',
    terms: false,
    newsletter: false,
  });

  const [errors, setErrors] = useState({});
  const [passwordStrength, setPasswordStrength] = useState(0);
  const [status, setStatus] = useState({
    submitting: false,
    submitted: false,
    error: false,
  });

  // Calculate password strength
  useEffect(() => {
    if (formData.password) {
      const strength = calculatePasswordStrength(formData.password);
      setPasswordStrength(strength);
    } else {
      setPasswordStrength(0);
    }
  }, [formData.password]);

  const calculatePasswordStrength = (password) => {
    let strength = 0;
    if (password.length >= 8) strength++;
    if (password.length >= 12) strength++;
    if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
    if (/[0-9]/.test(password)) strength++;
    if (/[^a-zA-Z0-9]/.test(password)) strength++;
    return strength;
  };

  const validateForm = () => {
    const newErrors = {};

    if (formData.name.trim().length < 2) {
      newErrors.name = 'Please enter your full name';
    }

    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(formData.email)) {
      newErrors.email = 'Please enter a valid email address';
    }

    if (passwordStrength < 2) {
      newErrors.password = 'Password is too weak';
    }

    if (formData.password !== formData.confirmPassword) {
      newErrors.confirmPassword = 'Passwords do not match';
    }

    if (!formData.terms) {
      newErrors.terms = 'You must accept the terms and conditions';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));

    // Clear error when user starts typing
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    if (!validateForm()) return;

    setStatus({ submitting: true, submitted: false, error: false });

    try {
      const submitData = new FormData();
      Object.keys(formData).forEach(key => {
        if (key !== 'confirmPassword') {
          submitData.append(key, formData[key]);
        }
      });

      submitData.append('apiKey', 'YOUR_API_KEY_HERE');
      submitData.append('subject', 'New User Registration');
      submitData.append('replyTo', formData.email);

      const response = await fetch('https://api.staticforms.dev/submit', {
        method: 'POST',
        body: submitData,
      });

      const result = await response.json();

      if (result.success) {
        setStatus({ submitting: false, submitted: true, error: false });
        setFormData({
          name: '',
          email: '',
          password: '',
          confirmPassword: '',
          terms: false,
          newsletter: false,
        });
      } else {
        throw new Error('Registration failed');
      }
    } catch (error) {
      setStatus({ submitting: false, submitted: false, error: true });
      console.error('Error:', error);
    }
  };

  const getStrengthClass = () => {
    if (passwordStrength === 0) return '';
    if (passwordStrength <= 2) return 'weak';
    if (passwordStrength <= 3) return 'medium';
    return 'strong';
  };

  return (
    <div className="registration-container">
      <div className="form-header">
        <h1>Create Your Account</h1>
        <p>Join thousands of users already using our platform</p>
      </div>

      {status.submitted && (
        <div className="alert alert-success">
          ✅ Registration successful! Please check your email to verify your account.
        </div>
      )}

      {status.error && (
        <div className="alert alert-error">
          ❌ Something went wrong. Please try again.
        </div>
      )}

      <form onSubmit={handleSubmit}>
        <div className="form-group">
          <label htmlFor="name">
            Full Name <span className="required">*</span>
          </label>
          <input
            type="text"
            id="name"
            name="name"
            value={formData.name}
            onChange={handleChange}
            className={errors.name ? 'error' : ''}
            required
            placeholder="John Doe"
          />
          {errors.name && <div className="error-message">{errors.name}</div>}
        </div>

        <div className="form-group">
          <label htmlFor="email">
            Email Address <span className="required">*</span>
          </label>
          <input
            type="email"
            id="email"
            name="email"
            value={formData.email}
            onChange={handleChange}
            className={errors.email ? 'error' : ''}
            required
            placeholder="john@example.com"
          />
          {errors.email && <div className="error-message">{errors.email}</div>}
        </div>

        <div className="form-group">
          <label htmlFor="password">
            Password <span className="required">*</span>
          </label>
          <input
            type="password"
            id="password"
            name="password"
            value={formData.password}
            onChange={handleChange}
            className={errors.password ? 'error' : ''}
            required
            placeholder="Create a strong password"
          />
          <div className="password-strength">
            <div className={`password-strength-bar ${getStrengthClass()}`}></div>
          </div>
          <div className="password-hint">
            At least 8 characters with uppercase, lowercase, and numbers
          </div>
          {errors.password && <div className="error-message">{errors.password}</div>}
        </div>

        <div className="form-group">
          <label htmlFor="confirmPassword">
            Confirm Password <span className="required">*</span>
          </label>
          <input
            type="password"
            id="confirmPassword"
            name="confirmPassword"
            value={formData.confirmPassword}
            onChange={handleChange}
            className={errors.confirmPassword ? 'error' : ''}
            required
            placeholder="Re-enter your password"
          />
          {errors.confirmPassword && <div className="error-message">{errors.confirmPassword}</div>}
        </div>

        <div className="checkbox-group">
          <input
            type="checkbox"
            id="terms"
            name="terms"
            checked={formData.terms}
            onChange={handleChange}
            required
          />
          <label htmlFor="terms">
            I agree to the <a href="/terms" target="_blank" rel="noopener noreferrer">Terms of Service</a> and <a href="/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy</a> <span className="required">*</span>
          </label>
        </div>

        <div className="checkbox-group">
          <input
            type="checkbox"
            id="newsletter"
            name="newsletter"
            checked={formData.newsletter}
            onChange={handleChange}
          />
          <label htmlFor="newsletter">
            Send me product updates and occasional emails (optional)
          </label>
        </div>

        <button type="submit" disabled={status.submitting}>
          {status.submitting ? 'Creating Account...' : 'Create Account'}
        </button>
      </form>

      <div className="login-link">
        Already have an account? <a href="/login">Sign in</a>
      </div>
    </div>
  );
}

export default RegistrationForm;

Vue.js Registration Form Example

For Vue.js applications (using Composition API):

Vue
<template>
  <div class="registration-container">
    <div class="form-header">
      <h1>Create Your Account</h1>
      <p>Join thousands of users already using our platform</p>
    </div>

    <div v-if="status.submitted" class="alert alert-success">
      ✅ Registration successful! Please check your email to verify your account.
    </div>

    <div v-if="status.error" class="alert alert-error">
      ❌ Something went wrong. Please try again.
    </div>

    <form @submit.prevent="handleSubmit">
      <div class="form-group">
        <label for="name">
          Full Name <span class="required">*</span>
        </label>
        <input
          type="text"
          id="name"
          v-model="formData.name"
          :class="{ error: errors.name }"
          required
          placeholder="John Doe"
        />
        <div v-if="errors.name" class="error-message">{{ errors.name }}</div>
      </div>

      <div class="form-group">
        <label for="email">
          Email Address <span class="required">*</span>
        </label>
        <input
          type="email"
          id="email"
          v-model="formData.email"
          :class="{ error: errors.email }"
          required
          placeholder="john@example.com"
        />
        <div v-if="errors.email" class="error-message">{{ errors.email }}</div>
      </div>

      <div class="form-group">
        <label for="password">
          Password <span class="required">*</span>
        </label>
        <input
          type="password"
          id="password"
          v-model="formData.password"
          :class="{ error: errors.password }"
          required
          placeholder="Create a strong password"
        />
        <div class="password-strength">
          <div
            class="password-strength-bar"
            :class="strengthClass"
          ></div>
        </div>
        <div class="password-hint">
          At least 8 characters with uppercase, lowercase, and numbers
        </div>
        <div v-if="errors.password" class="error-message">{{ errors.password }}</div>
      </div>

      <div class="form-group">
        <label for="confirmPassword">
          Confirm Password <span class="required">*</span>
        </label>
        <input
          type="password"
          id="confirmPassword"
          v-model="formData.confirmPassword"
          :class="{ error: errors.confirmPassword }"
          required
          placeholder="Re-enter your password"
        />
        <div v-if="errors.confirmPassword" class="error-message">{{ errors.confirmPassword }}</div>
      </div>

      <div class="checkbox-group">
        <input
          type="checkbox"
          id="terms"
          v-model="formData.terms"
          required
        />
        <label for="terms">
          I agree to the <a href="/terms" target="_blank">Terms of Service</a> and
          <a href="/privacy" target="_blank">Privacy Policy</a>
          <span class="required">*</span>
        </label>
      </div>

      <div class="checkbox-group">
        <input
          type="checkbox"
          id="newsletter"
          v-model="formData.newsletter"
        />
        <label for="newsletter">
          Send me product updates and occasional emails (optional)
        </label>
      </div>

      <button type="submit" :disabled="status.submitting">
        {{ status.submitting ? 'Creating Account...' : 'Create Account' }}
      </button>
    </form>

    <div class="login-link">
      Already have an account? <a href="/login">Sign in</a>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch } from 'vue';

const formData = ref({
  name: '',
  email: '',
  password: '',
  confirmPassword: '',
  terms: false,
  newsletter: false,
});

const errors = ref({});
const passwordStrength = ref(0);
const status = ref({
  submitting: false,
  submitted: false,
  error: false,
});

// Calculate password strength
watch(() => formData.value.password, (newPassword) => {
  if (newPassword) {
    let strength = 0;
    if (newPassword.length >= 8) strength++;
    if (newPassword.length >= 12) strength++;
    if (/[a-z]/.test(newPassword) && /[A-Z]/.test(newPassword)) strength++;
    if (/[0-9]/.test(newPassword)) strength++;
    if (/[^a-zA-Z0-9]/.test(newPassword)) strength++;
    passwordStrength.value = strength;
  } else {
    passwordStrength.value = 0;
  }
});

const strengthClass = computed(() => {
  if (passwordStrength.value === 0) return '';
  if (passwordStrength.value <= 2) return 'weak';
  if (passwordStrength.value <= 3) return 'medium';
  return 'strong';
});

const validateForm = () => {
  const newErrors = {};

  if (formData.value.name.trim().length < 2) {
    newErrors.name = 'Please enter your full name';
  }

  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(formData.value.email)) {
    newErrors.email = 'Please enter a valid email address';
  }

  if (passwordStrength.value < 2) {
    newErrors.password = 'Password is too weak';
  }

  if (formData.value.password !== formData.value.confirmPassword) {
    newErrors.confirmPassword = 'Passwords do not match';
  }

  if (!formData.value.terms) {
    newErrors.terms = 'You must accept the terms and conditions';
  }

  errors.value = newErrors;
  return Object.keys(newErrors).length === 0;
};

const handleSubmit = async () => {
  if (!validateForm()) return;

  status.value = { submitting: true, submitted: false, error: false };

  try {
    const submitData = new FormData();
    Object.keys(formData.value).forEach(key => {
      if (key !== 'confirmPassword') {
        submitData.append(key, formData.value[key]);
      }
    });

    submitData.append('apiKey', 'YOUR_API_KEY_HERE');
    submitData.append('subject', 'New User Registration');
    submitData.append('replyTo', formData.value.email);

    const response = await fetch('https://api.staticforms.dev/submit', {
      method: 'POST',
      body: submitData,
    });

    const result = await response.json();

    if (result.success) {
      status.value = { submitting: false, submitted: true, error: false };
      formData.value = {
        name: '',
        email: '',
        password: '',
        confirmPassword: '',
        terms: false,
        newsletter: false,
      };
    } else {
      throw new Error('Registration failed');
    }
  } catch (error) {
    status.value = { submitting: false, submitted: false, error: true };
    console.error('Error:', error);
  }
};
</script>

Security Best Practices

1. Never Store Passwords in Plain Text

  • Always hash passwords server-side
  • Use bcrypt, Argon2, or similar
  • Never send plain passwords via email

2. Implement Rate Limiting

  • Limit registration attempts per IP
  • Prevent automated bot registrations
  • Use CAPTCHA for suspicious activity

3. Validate on Both Client and Server

  • Client-side validation for UX
  • Server-side validation for security
  • Never trust client-side data

4. Use HTTPS Always

  • Encrypt all data in transit
  • Enable HSTS headers
  • Use secure cookies

5. Implement CAPTCHA Protection

Add reCAPTCHA or Altcha to prevent bot registrations:

HTML
<!-- Altcha (privacy-first) -->
<script type="module" src="https://cdn.jsdelivr.net/npm/altcha/dist/altcha.min.js"></script>

<form>
  <!-- Your form fields -->

  <altcha-widget
    challengeurl="https://api.staticforms.dev/altcha/challenge"
    apikey="YOUR_API_KEY"
  ></altcha-widget>

  <button type="submit">Register</button>
</form>

GDPR Compliance Considerations

When building registration forms for users in the EU, ensure GDPR compliance:

  • Separate consent for terms vs. marketing emails
  • No pre-checked boxes for optional consents
  • Clear, plain language explanations

2. Data Minimization

  • Only collect necessary data
  • Explain why each field is required
  • Don't collect data "just in case"

3. Right to Access & Deletion

  • Provide data export functionality
  • Allow account deletion
  • Honor deletion requests promptly

4. Privacy Policy

  • Link to detailed privacy policy
  • Explain data usage clearly
  • List third parties with access

5. Secure Data Handling

  • Encrypt data at rest and in transit
  • Limit data access
  • Regular security audits

Getting Your Static Forms API Key

To use Static Forms with your registration form:

  1. Sign up for free at Static Forms
  2. Create a new form in your dashboard
  3. Copy your API key
  4. Replace YOUR_API_KEY_HERE in the code examples

Using Environment Variables

React/Next.js (.env.local):

Bash
NEXT_PUBLIC_STATICFORMS_KEY=your_api_key_here

Vue.js (.env):

Bash
VUE_APP_STATICFORMS_KEY=your_api_key_here

Then use:

JavaScript
process.env.NEXT_PUBLIC_STATICFORMS_KEY  // Next.js
process.env.VUE_APP_STATICFORMS_KEY      // Vue.js

Common Issues & Solutions

Password Validation Too Strict

Problem: Users struggle to create valid passwords

Solution:

  • Show requirements clearly before they start
  • Provide real-time feedback
  • Allow paste functionality
  • Consider passphrase support

Email Verification Not Working

Problem: Users don't receive verification emails

Solution:

  • Check spam folders
  • Use recognizable sender name
  • Send email immediately after registration
  • Include resend option

Terms Checkbox Not Visible

Problem: Users miss the terms checkbox

Solution:

  • Make checkbox and label clearly visible
  • Use adequate contrast
  • Position prominently
  • Block submission with clear error if unchecked

Conclusion

You now have everything needed to build secure, user-friendly registration forms with email verification, password validation, and GDPR compliance. Whether you're using plain HTML, React, or Vue.js, these patterns will help you create professional registration experiences.

Static Forms handles all the backend complexity—email delivery, data storage, spam protection—so you can focus on building great user experiences.

Next Steps

Ready to start collecting registrations? Get started with Static Forms today and have your registration form live in minutes.