Of course! Here is a complete, self-contained example of a modern web form using HTML, CSS, and JavaScript.
Design Philosophy
This form incorporates several modern web design trends:
- Minimalist Aesthetic: A clean, card-based design with plenty of white space.
- Responsive Design: It looks great on desktops, tablets, and mobile devices.
- Enhanced User Experience (UX):
- Clear, floating labels that move out of the way when you type.
- Real-time validation feedback with helpful error messages.
- A visually engaging submit button with a loading state.
- A clear success message upon completion.
- CSS Variables: For easy theme customization (changing colors is simple).
- Google Fonts: Using a clean, modern font for better typography.
Live Demo
You can see a live version of this form here:
CodePen Live Demo
The Code
You can create three files: index.html
, style.css
, and script.js
.
1. HTML (index.html
)
This file provides the structure of the form. We use <label>
tags for accessibility and group related elements in <div>
s for styling.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Express Interest in Our Product</title>
<link rel="stylesheet" href="style.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="form-container">
<div id="form-content">
<div class="form-header">
<h2>Get Early Access</h2>
<p>Join our waiting list to be the first to know when we launch.</p>
</div>
<form id="interest-form" novalidate>
<div class="form-group">
<input type="text" id="name" name="name" required>
<label for="name">Full Name</label>
<div class="error-message" id="name-error"></div>
</div>
<div class="form-group">
<input type="email" id="email" name="email" required>
<label for="email">Email Address</label>
<div class="error-message" id="email-error"></div>
</div>
<div class="form-group">
<select id="source" name="source" required>
<option value="" disabled selected></option>
<option value="social-media">Social Media</option>
<option value="friend">From a Friend</option>
<option value="search-engine">Search Engine (Google, etc.)</option>
<option value="advertisement">Advertisement</option>
<option value="other">Other</option>
</select>
<label for="source">How did you hear about us?</label>
<div class="error-message" id="source-error"></div>
</div>
<button type="submit" id="submit-btn">
<span class="btn-text">Count Me In!</span>
<span class="btn-loader" style="display: none;"></span>
</button>
</form>
</div>
<div id="success-message" style="display: none;">
<h3>Thank You! 🎉</h3>
<p>You're on the list. We'll be in touch shortly.</p>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
2. CSS (style.css
)
This file handles the visual styling, responsiveness, and animations.
/* --- CSS Variables for Easy Theming --- */
:root {
--primary-color: #6a5af9;
--primary-hover-color: #5548c7;
--background-color: #f4f7fc;
--card-background: #ffffff;
--text-color: #333;
--label-color: #888;
--error-color: #d93025;
--border-color: #ddd;
--border-focus-color: #6a5af9;
}
/* --- General Styles --- */
body {
font-family: 'Inter', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
box-sizing: border-box;
}
.form-container {
background-color: var(--card-background);
padding: 40px;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 480px;
transition: all 0.3s ease-in-out;
}
/* --- Form Header --- */
.form-header {
text-align: center;
margin-bottom: 30px;
}
.form-header h2 {
margin: 0 0 10px;
font-size: 28px;
font-weight: 700;
}
.form-header p {
margin: 0;
color: var(--label-color);
font-size: 16px;
}
/* --- Form Group & Floating Labels --- */
.form-group {
position: relative;
margin-bottom: 25px;
}
.form-group input,
.form-group select {
width: 100%;
padding: 14px;
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 16px;
background-color: transparent;
transition: border-color 0.2s;
-webkit-appearance: none; /* Removes default styling for select on iOS */
-moz-appearance: none;
appearance: none;
}
.form-group select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 1rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
}
.form-group label {
position: absolute;
top: 15px;
left: 15px;
color: var(--label-color);
pointer-events: none;
transition: all 0.2s ease-out;
background-color: var(--card-background);
padding: 0 5px;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: var(--border-focus-color);
}
/* Floating label effect */
.form-group input:focus + label,
.form-group input:not(:placeholder-shown) + label,
.form-group select:focus + label,
.form-group select:valid + label {
top: -10px;
left: 10px;
font-size: 12px;
color: var(--primary-color);
}
/* --- Error Handling --- */
.error-message {
color: var(--error-color);
font-size: 12px;
margin-top: 5px;
min-height: 15px; /* Prevents layout shift */
}
.form-group input.invalid,
.form-group select.invalid {
border-color: var(--error-color);
}
.form-group input.invalid + label,
.form-group select.invalid + label {
color: var(--error-color);
}
/* --- Submit Button --- */
button[type="submit"] {
width: 100%;
padding: 15px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
display: flex;
justify-content: center;
align-items: center;
}
button[type="submit"]:hover {
background-color: var(--primary-hover-color);
}
button[type="submit"]:active {
transform: scale(0.98);
}
/* --- Loader Animation --- */
.btn-loader {
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top: 3px solid #fff;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* --- Success Message --- */
#success-message {
text-align: center;
}
#success-message h3 {
font-size: 24px;
color: var(--primary-color);
margin-bottom: 10px;
}
/* --- Responsive Design --- */
@media (max-width: 520px) {
.form-container {
padding: 25px;
}
.form-header h2 {
font-size: 24px;
}
}
3. JavaScript (script.js
)
This file handles the form validation and submission logic.
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('interest-form');
const nameInput = document.getElementById('name');
const emailInput = document.getElementById('email');
const sourceSelect = document.getElementById('source');
const submitBtn = document.getElementById('submit-btn');
const btnText = document.querySelector('.btn-text');
const btnLoader = document.querySelector('.btn-loader');
// --- VALIDATION FUNCTIONS ---
const showError = (input, message) => {
const formGroup = input.parentElement;
const errorDiv = formGroup.querySelector('.error-message');
input.classList.add('invalid');
errorDiv.textContent = message;
};
const clearError = (input) => {
const formGroup = input.parentElement;
const errorDiv = formGroup.querySelector('.error-message');
input.classList.remove('invalid');
errorDiv.textContent = '';
};
const validateName = () => {
clearError(nameInput);
if (nameInput.value.trim() === '') {
showError(nameInput, 'Name is required.');
return false;
}
return true;
};
const validateEmail = () => {
clearError(emailInput);
const emailValue = emailInput.value.trim();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (emailValue === '') {
showError(emailInput, 'Email is required.');
return false;
}
if (!emailRegex.test(emailValue)) {
showError(emailInput, 'Please enter a valid email address.');
return false;
}
return true;
};
const validateSource = () => {
clearError(sourceSelect);
if (sourceSelect.value === '') {
showError(sourceSelect, 'Please select an option.');
return false;
}
return true;
};
// --- REAL-TIME VALIDATION ---
nameInput.addEventListener('input', validateName);
emailInput.addEventListener('input', validateEmail);
sourceSelect.addEventListener('change', validateSource);
// --- FORM SUBMISSION ---
form.addEventListener('submit', function (event) {
event.preventDefault(); // Prevent default form submission
// Run all validations
const isNameValid = validateName();
const isEmailValid = validateEmail();
const isSourceValid = validateSource();
// If all fields are valid, proceed
if (isNameValid && isEmailValid && isSourceValid) {
// Show loading state
btnText.style.display = 'none';
btnLoader.style.display = 'block';
submitBtn.disabled = true;
// Collect form data
const formData = {
name: nameInput.value.trim(),
email: emailInput.value.trim(),
source: sourceSelect.value
};
console.log('Form Data Submitted:', formData);
// --- SIMULATE API CALL ---
// In a real application, you would send `formData` to your server here.
// e.g., using fetch():
// fetch('/api/subscribe', { method: 'POST', body: JSON.stringify(formData), ... })
setTimeout(() => {
// Hide the form and show the success message
document.getElementById('form-content').style.display = 'none';
document.getElementById('success-message').style.display = 'block';
}, 1500); // Simulate a 1.5-second network request
}
});
});
How to Use It
- Save the files: Create the three files (
index.html
, style.css
, script.js
) in the same folder.
- Open in browser: Open
index.html
in any modern web browser.
- Test: Try submitting the form with empty or invalid data to see the validation in action. Then, fill it out correctly to see the success state.
Next Steps (Making it Functional)
Right now, the form data is only logged to the browser's console. To make it truly useful, you need to send the data to a server.
In the script.js
file, replace the // --- SIMULATE API CALL ---
section with a real fetch
request to your backend endpoint. Your backend could then:
- Save the data to a database.
- Add the email to a mailing list (like Mailchimp or SendGrid).
- Send you a notification email.