Learn how to create a modern HTML bank account opening form using HTML, CSS, and JavaScript with step-by-step guide and code examples.
Table of Contents
Almost every bank provides online forms for opening a new account. A bank account opening form in HTML is a perfect project for beginners to practice form design, validation, and modern UI techniques.
In this blog, we will:
- Create a form with HTML
- Style it with CSS (modern glassmorphism + responsive design)
- Add validation using JavaScript
Before starting, let’s check the prerequisites.
Prerequisites
To follow along, you should have:
- Basic knowledge of HTML (input, labels, forms)
- Basic knowledge of CSS (styling, layout, effects)
- Beginner-level JavaScript (form validation)
- A code editor (VS Code recommended)
- A web browser (Chrome, Edge, or Firefox)
Source Code
Step 1 (HTML Code):
The first step is HTML. HTML helps us create the structure and layout of the form. We will use Tailwind CSS to design a clean and responsive interface. Let's breakdown the HTML code step by step:
1. Document Setup
<!DOCTYPE html>
<html lang="en">
<head>
- Declares this is an HTML5 document.
- lang="en" sets the language to English.
<meta charset="UTF-8">ensures proper character encoding.<meta name="viewport"...>makes it responsive on mobile.
2. External Resources
<script src="https://cdn.tailwindcss.com"></script>
- Tailwind CSS CDN → Provides modern utility-based styling without writing CSS from scratch.
<link href="https://fonts.googleapis.com/css2?family=Inter...">
- Google Fonts (Inter) → Gives a clean, professional typography.
<script src="https://unpkg.com/lucide@latest"></script>
- Lucide Icons → Provides SVG-based icons (user, lock, email, etc.).
<link rel="stylesheet" href="styles.css">
- Links to a custom stylesheet for extra styling.
3. Body Styling
<body class="bg-gradient-to-br from-slate-50 to-indigo-100 flex items-center justify-center min-h-screen p-4">
- Background Gradient → from light gray (slate-50) to soft indigo.
- Uses Flexbox → centers the form horizontally & vertically.
- Padding (p-4) ensures spacing on smaller screens.
4. Main Layout
<main class="w-full max-w-6xl mx-auto bg-white/60 rounded-2xl shadow-2xl flex flex-col md:flex-row min-h-[90vh] glass-panel">
- Responsive container (max-w-6xl) with rounded corners.
- Glassmorphism effect (bg-white/60 with transparency).
- Shadow (shadow-2xl) gives a premium card look.
- Split into two panels: left (branding & stepper) and right (form).
5. Left Panel → Branding + Stepper
- Dark background with gradient (from-slate-800 to-slate-900).
- Contains:
- Logo + Bank Name ("Apex Financial")
- Tagline ("Your journey to financial freedom...")
- Vertical Stepper → Shows progress across 4 steps:
- Personal Info
- Address Details
- Security Setup
- Confirmation
- Uses Lucide icons for visual appeal.
- Footer → copyright.
6. Right Panel → Multi-Step Form
This is the main form where users enter details. It uses step-based navigation (fieldset per step).
Step 1 – Personal Information
Collects:
- First Name, Last Name
- Email, Phone
- Date of Birth, Gender
- ID Number & ID Proof upload
Each input uses:
- Floating labels (label moves when typing).
- Lucide icons inside input fields.
- Validation messages (hidden until error).
Step 2 – Address Details
Collects:
- Street Address, City, State
- Zip / Postal Code, Country
- Account Type (Savings, Current, Investment)
Step 3 – Security Setup
- Password & Confirm Password fields.
- Shows an error if passwords don’t match or are too short.
Step 4 – Confirmation
- Displays a summary of all entered details for review.
- Checkbox to accept Terms & Conditions.
7. Navigation Buttons
<button type="button" id="prev-btn">Previous</button>
<button type="button" id="next-btn">Next Step</button>
<button type="submit" id="submit-btn" class="hidden">Submit Application</button>
- Previous & Next Step → navigate between steps.
- Submit → only visible on the final step.
8. Success Message
<div id="success-message" class="hidden">
<i data-lucide="party-popper"></i>
<h2>Application Submitted!</h2>
<p>Your reference number is: <span id="ref-number"></span></p>
</div>
- Shown after successful submission.
- Displays a thank you message + reference number.
9. JavaScript (script.js)
<script src="script.js"></script>
- Handles form validation, step navigation, file uploads, and showing a success message.
Step 2 (CSS Code):
After HTML, we style the form using CSS. Even though Tailwind handles most design, we can add custom CSS for floating labels, gradients, and hover effects.
1. Global Font
body {
font-family: 'Inter', sans-serif;
}
- Sets the entire page font to Inter (with a fallback of any sans-serif font).
2. Form Step Animation
.form-step {
display: none;
animation: fadeIn 0.5s ease-in-out;
}
.form-step.active {
display: block;
}
- By default, form steps (.form-step) are hidden (display: none).
- When .active is applied, the step becomes visible (display: block) and plays a fade-in animation.
3. Fade In Keyframes
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
- Defines a smooth animation:
- Starts transparent (opacity: 0) and shifted 30px to the right.
- Ends fully visible (opacity: 1) at its original position.
4. Glassmorphism Effect
.glass-panel {
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
- Creates a frosted glass effect:
- Transparent white background (rgba(255, 255, 255, 0.6)).
- Blurred background (works only if something is behind it).
- Soft border for realism.
5. Input Field with Icon
.input-icon {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
color: #9ca3af; /* gray-400 */
pointer-events: none;
}
.input-field {
padding-left: 3rem;
}
- .input-icon: Places an icon inside an input box on the left side, vertically centered.
- .input-field: Adds extra left padding so the text doesn’t overlap the icon.
6. Floating Label Style
.floating-label {
position: absolute;
top: 50%;
left: 3rem;
transform: translateY(-50%);
color: #6b7280;
pointer-events: none;
transition: all 0.2s ease-out;
background-color: transparent;
padding: 0 0.25rem;
}
- Initially, the label sits inside the input field, looking like placeholder text.
- Uses transitions so it moves smoothly when activated.
.input-field:focus ~ .floating-label,
.input-field:not(:placeholder-shown) ~ .floating-label,
.input-field.has-value ~ .floating-label {
top: 0;
left: 2.75rem;
transform: translateY(-50%) scale(0.85);
color: #3b82f6;
background-color: white;
}
- When input is focused OR has text, the label moves up, shrinks slightly (scale(0.85)), and changes color to blue (#3b82f6).
7. Gradient Button
.btn-gradient {
background-image: linear-gradient(to right, #3b82f6, #6366f1);
transition: all 0.3s ease;
}
.btn-gradient:hover {
box-shadow: 0 10px 15px -3px rgba(99, 102, 241, 0.4),
0 4px 6px -2px rgba(99, 102, 241, 0.2);
transform: translateY(-2px);
}
- Creates a blue-to-purple gradient button.
- On hover:
- Adds a soft, glowing shadow.
- Button moves slightly upward (translateY(-2px)), giving a 3D effect.
8. Active Step Icon Glow
.step-icon-active {
box-shadow: 0 0 15px 3px rgba(59, 130, 246, 0.6);
}
- Adds a blue glowing effect to highlight the current step in a form or progress indicator.
body {
font-family: 'Inter', sans-serif;
}
.form-step {
display: none;
animation: fadeIn 0.5s ease-in-out;
}
.form-step.active {
display: block;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Glassmorphism Effect */
.glass-panel {
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* Input field with icon padding */
.input-icon {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
color: #9ca3af; /* gray-400 */
pointer-events: none;
}
.input-field {
padding-left: 3rem;
}
/* Floating Label Styles */
.floating-label {
position: absolute;
top: 50%;
left: 3rem;
transform: translateY(-50%);
color: #6b7280;
pointer-events: none;
transition: all 0.2s ease-out;
background-color: transparent;
padding: 0 0.25rem;
}
.input-field:focus ~ .floating-label,
.input-field:not(:placeholder-shown) ~ .floating-label,
.input-field.has-value ~ .floating-label {
top: 0;
left: 2.75rem;
transform: translateY(-50%) scale(0.85);
color: #3b82f6;
background-color: white;
}
/* Gradient Button */
.btn-gradient {
background-image: linear-gradient(to right, #3b82f6, #6366f1);
transition: all 0.3s ease;
}
.btn-gradient:hover {
box-shadow: 0 10px 15px -3px rgba(99, 102, 241, 0.4), 0 4px 6px -2px rgba(99, 102, 241, 0.2);
transform: translateY(-2px);
}
/* Active Step Icon Glow */
.step-icon-active {
box-shadow: 0 0 15px 3px rgba(59, 130, 246, 0.6);
} Step 3 (JavaScript Code):
The final step is JavaScript. Here we add functions to validate the form and handle submissions.
1. Initial Setup
document.addEventListener('DOMContentLoaded', () => {
lucide.createIcons();
const form = document.getElementById('account-form');
const formSteps = Array.from(form.querySelectorAll('.form-step'));
const nextBtn = document.getElementById('next-btn');
const prevBtn = document.getElementById('prev-btn');
const submitBtn = document.getElementById('submit-btn');
const progressSteps = Array.from(document.querySelectorAll('.progress-step'));
const idProofInput = document.getElementById('idProof');
const fileChosenSpan = document.getElementById('file-chosen');
- Runs the script after the DOM is fully loaded.
- Initializes variables:
- formSteps: all step sections in the form.
- nextBtn, prevBtn, submitBtn: navigation buttons.
- progressSteps: progress bar circles/steps.
- idProofInput: file upload input for ID proof.
- fileChosenSpan: shows the chosen file name.
- lucide.createIcons(): renders Lucide icons (used for step indicators).
2. Floating Select Field Support
document.querySelectorAll('select.input-field').forEach(select => {
select.addEventListener('change', e => {
if (e.target.value) {
e.target.classList.add('has-value');
} else {
e.target.classList.remove('has-value');
}
});
});
- Ensures select dropdowns behave like floating labels.
- If a value is chosen, it adds a has-value class (moves the label up).
3. Step Tracker
let currentStep = 0;
- Keeps track of the current form step (starting at step 0).
4. Update Form UI
const updateFormUI = () => {
formSteps.forEach((step, index) => step.classList.toggle('active', index === currentStep));
progressSteps.forEach((step, index) => {
const iconDiv = step.querySelector('.step-icon');
const text = step.querySelector('h2');
iconDiv.classList.remove('bg-green-500', 'border-green-500', 'bg-blue-600', 'border-blue-600', 'bg-slate-700', 'border-slate-600', 'text-white', 'step-icon-active');
if (index < currentStep) { // Completed
iconDiv.classList.add('bg-green-500', 'border-green-500', 'text-white');
text.classList.remove('text-slate-300');
text.classList.add('text-white');
} else if (index === currentStep) { // Active
iconDiv.classList.add('bg-blue-600', 'border-blue-600', 'text-white', 'step-icon-active');
text.classList.remove('text-slate-300');
text.classList.add('text-white');
} else { // Future
iconDiv.classList.add('bg-slate-700', 'border-slate-600');
text.classList.add('text-slate-300');
text.classList.remove('text-white');
}
});
prevBtn.style.display = currentStep === 0 ? 'none' : 'inline-block';
nextBtn.style.display = currentStep === formSteps.length - 1 ? 'none' : 'inline-block';
submitBtn.style.display = currentStep === formSteps.length - 1 ? 'inline-block' : 'none';
if (currentStep === formSteps.length - 1) {
updateSummary();
}
};
- Controls which step is visible.
- Updates progress bar styling:
- Green for completed steps.
- Blue glow for active step.
- Gray for upcoming steps.
- Shows/hides navigation buttons correctly.
- Calls updateSummary() at the final step.
5. Validation
const validateStep = (stepIndex) => {
let isValid = true;
const currentStepFields = formSteps[stepIndex].querySelectorAll('input[required], select[required]');
currentStepFields.forEach(field => {
let parentElement = field.parentElement;
if (field.type === 'checkbox') parentElement = parentElement.parentElement;
if (field.type === 'file') parentElement = field.parentElement.parentElement;
const errorMsg = parentElement.querySelector('.error-message');
let fieldValid = true;
if (field.type === 'checkbox' && !field.checked) {
fieldValid = false;
errorMsg.textContent = "You must agree to the terms.";
} else if (field.type === 'email') {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!field.value.trim() || !emailRegex.test(field.value)) {
fieldValid = false;
errorMsg.textContent = "Please enter a valid email address.";
}
} else if (field.type === 'password' && field.id === 'password') {
if (field.value.length < 8) {
fieldValid = false;
errorMsg.textContent = "Password must be at least 8 characters.";
}
} else if (field.type === 'password' && field.id === 'confirmPassword') {
const password = document.getElementById('password').value;
if (field.value !== password) {
fieldValid = false;
errorMsg.textContent = "Passwords do not match.";
}
} else if (field.type === 'file' && field.files.length === 0) {
fieldValid = false;
errorMsg.textContent = "Please upload your ID proof.";
} else if (!field.value.trim() && field.type !== 'file' && field.type !== 'date') {
if(field.tagName.toLowerCase() === 'select' && field.value === "") {
fieldValid = false;
} else if (field.tagName.toLowerCase() === 'input' && !field.value.trim()){
fieldValid = false;
}
if(!fieldValid) errorMsg.textContent = "This field is required.";
}
if (!fieldValid) {
isValid = false;
errorMsg.classList.remove('hidden');
if (field.type !== 'file' && field.type !== 'checkbox') {
field.classList.add('border-red-500');
field.classList.remove('border-gray-300');
}
} else {
errorMsg.classList.add('hidden');
if (field.type !== 'file' && field.type !== 'checkbox') {
field.classList.remove('border-red-500');
field.classList.add('border-gray-300');
}
}
});
return isValid;
};
- Validates inputs in the current step:
- Checkbox → must be checked.
- Email → must match email regex.
- Password → min 8 characters, must match confirm password.
- File upload → must have a file.
- Other required fields → cannot be empty.
- Shows error messages + red borders if invalid.
6. Summary Generator
const updateSummary = () => {
const summaryContainer = document.getElementById('summary-details');
const formData = new FormData(form);
let summaryHTML = '';
const fieldLabels = {
firstName: 'First Name', lastName: 'Last Name', email: 'Email Address', phone: 'Phone Number', dob: 'Date of Birth', gender: 'Gender', identificationNumber: 'ID Number', idProof: 'ID Proof',
address: 'Street Address', city: 'City', zip: 'ZIP Code', state: 'State', country: 'Country', accountType: 'Account Type'
};
for (let [key, value] of formData.entries()) {
if (key.includes('password') || key === 'terms' || !value) continue;
const label = fieldLabels[key] || key;
let displayValue = value instanceof File ? value.name : value;
summaryHTML += `<div class="flex justify-between items-center py-1"><span class="font-medium text-gray-800">${label}:</span><span class="text-right">${displayValue}</span></div>`;
}
summaryContainer.innerHTML = summaryHTML;
};
- Collects form data.
- Builds a readable summary (ignores passwords & terms checkbox).
- Displays file uploads by filename.
- Outputs summary inside #summary-details.
7. Navigation + Submission
nextBtn.addEventListener('click', () => {
if (validateStep(currentStep)) {
if (currentStep < formSteps.length - 1) {
currentStep++;
updateFormUI();
}
}
});
prevBtn.addEventListener('click', () => {
if (currentStep > 0) {
currentStep--;
updateFormUI();
}
});
form.addEventListener('submit', (e) => {
e.preventDefault();
if (validateStep(currentStep)) {
document.getElementById('form-container').classList.add('hidden');
const successMsg = document.getElementById('success-message');
successMsg.classList.remove('hidden');
successMsg.classList.add('active'); // apply animation
document.getElementById('ref-number').textContent = `APX${Date.now()}`;
lucide.createIcons(); // Re-render icons for success message
}
});
- Next → validates, moves forward if valid.
- Previous → goes back one step.
- Submit:
- Prevents default form submission.
- If valid, hides the form and shows a success message.
- Generates a unique reference number (APX + timestamp).
8. File Upload Display
idProofInput.addEventListener('change', () => {
fileChosenSpan.textContent = idProofInput.files[0] ? idProofInput.files[0].name : 'Choose a file...';
});
- Updates the file name display when the user uploads ID proof.
document.addEventListener('DOMContentLoaded', () => {
lucide.createIcons();
const form = document.getElementById('account-form');
const formSteps = Array.from(form.querySelectorAll('.form-step'));
const nextBtn = document.getElementById('next-btn');
const prevBtn = document.getElementById('prev-btn');
const submitBtn = document.getElementById('submit-btn');
const progressSteps = Array.from(document.querySelectorAll('.progress-step'));
const idProofInput = document.getElementById('idProof');
const fileChosenSpan = document.getElementById('file-chosen');
document.querySelectorAll('select.input-field').forEach(select => {
select.addEventListener('change', e => {
if (e.target.value) {
e.target.classList.add('has-value');
} else {
e.target.classList.remove('has-value');
}
});
});
let currentStep = 0;
const updateFormUI = () => {
formSteps.forEach((step, index) => step.classList.toggle('active', index === currentStep));
progressSteps.forEach((step, index) => {
const iconDiv = step.querySelector('.step-icon');
const text = step.querySelector('h2');
iconDiv.classList.remove('bg-green-500', 'border-green-500', 'bg-blue-600', 'border-blue-600', 'bg-slate-700', 'border-slate-600', 'text-white', 'step-icon-active');
if (index < currentStep) { // Completed
iconDiv.classList.add('bg-green-500', 'border-green-500', 'text-white');
text.classList.remove('text-slate-300');
text.classList.add('text-white');
} else if (index === currentStep) { // Active
iconDiv.classList.add('bg-blue-600', 'border-blue-600', 'text-white', 'step-icon-active');
text.classList.remove('text-slate-300');
text.classList.add('text-white');
} else { // Future
iconDiv.classList.add('bg-slate-700', 'border-slate-600');
text.classList.add('text-slate-300');
text.classList.remove('text-white');
}
});
prevBtn.style.display = currentStep === 0 ? 'none' : 'inline-block';
nextBtn.style.display = currentStep === formSteps.length - 1 ? 'none' : 'inline-block';
submitBtn.style.display = currentStep === formSteps.length - 1 ? 'inline-block' : 'none';
if (currentStep === formSteps.length - 1) {
updateSummary();
}
};
const validateStep = (stepIndex) => {
let isValid = true;
const currentStepFields = formSteps[stepIndex].querySelectorAll('input[required], select[required]');
currentStepFields.forEach(field => {
let parentElement = field.parentElement;
if (field.type === 'checkbox') parentElement = parentElement.parentElement;
if (field.type === 'file') parentElement = field.parentElement.parentElement;
const errorMsg = parentElement.querySelector('.error-message');
let fieldValid = true;
if (field.type === 'checkbox' && !field.checked) {
fieldValid = false;
errorMsg.textContent = "You must agree to the terms.";
} else if (field.type === 'email') {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!field.value.trim() || !emailRegex.test(field.value)) { fieldValid = false; errorMsg.textContent = "Please enter a valid email address.";}
} else if (field.type === 'password' && field.id === 'password') {
if (field.value.length < 8) { fieldValid = false; errorMsg.textContent = "Password must be at least 8 characters."; }
} else if (field.type === 'password' && field.id === 'confirmPassword') {
const password = document.getElementById('password').value;
if (field.value !== password) { fieldValid = false; errorMsg.textContent = "Passwords do not match."; }
} else if (field.type === 'file' && field.files.length === 0) {
fieldValid = false;
errorMsg.textContent = "Please upload your ID proof.";
} else if (!field.value.trim() && field.type !== 'file' && field.type !== 'date') {
if(field.tagName.toLowerCase() === 'select' && field.value === "") {
fieldValid = false;
} else if (field.tagName.toLowerCase() === 'input' && !field.value.trim()){
fieldValid = false;
}
if(!fieldValid) errorMsg.textContent = "This field is required.";
}
if (!fieldValid) {
isValid = false;
errorMsg.classList.remove('hidden');
if (field.type !== 'file' && field.type !== 'checkbox') {
field.classList.add('border-red-500');
field.classList.remove('border-gray-300');
}
} else {
errorMsg.classList.add('hidden');
if (field.type !== 'file' && field.type !== 'checkbox') {
field.classList.remove('border-red-500');
field.classList.add('border-gray-300');
}
}
});
return isValid;
};
const updateSummary = () => {
const summaryContainer = document.getElementById('summary-details');
const formData = new FormData(form);
let summaryHTML = '';
const fieldLabels = {
firstName: 'First Name', lastName: 'Last Name', email: 'Email Address', phone: 'Phone Number', dob: 'Date of Birth', gender: 'Gender', identificationNumber: 'ID Number', idProof: 'ID Proof',
address: 'Street Address', city: 'City', zip: 'ZIP Code', state: 'State', country: 'Country', accountType: 'Account Type'
};
for (let [key, value] of formData.entries()) {
if (key.includes('password') || key === 'terms' || !value) continue;
const label = fieldLabels[key] || key;
let displayValue = value instanceof File ? value.name : value;
summaryHTML += `<div class="flex justify-between items-center py-1"><span class="font-medium text-gray-800">${label}:</span><span class="text-right">${displayValue}</span></div>`;
}
summaryContainer.innerHTML = summaryHTML;
};
nextBtn.addEventListener('click', () => { if (validateStep(currentStep)) { if (currentStep < formSteps.length - 1) { currentStep++; updateFormUI(); } } });
prevBtn.addEventListener('click', () => { if (currentStep > 0) { currentStep--; updateFormUI(); } });
form.addEventListener('submit', (e) => {
e.preventDefault();
if (validateStep(currentStep)) {
document.getElementById('form-container').classList.add('hidden');
const successMsg = document.getElementById('success-message');
successMsg.classList.remove('hidden');
successMsg.classList.add('active'); // apply animation
document.getElementById('ref-number').textContent = `APX${Date.now()}`;
lucide.createIcons(); // Re-render icons for success message
}
});
idProofInput.addEventListener('change', () => { fileChosenSpan.textContent = idProofInput.files[0] ? idProofInput.files[0].name : 'Choose a file...'; });
updateFormUI();
});Final Output:
Conclusion:
We have successfully created a Bank Account Opening Form using HTML, CSS, and JavaScript. You learned how to design the form, style it with modern effects, and add JavaScript validation. This form can be further enhanced with backend integration (PHP, Node.js, or Python Django) to actually store the data.
This project is a great practice for beginners in web development.
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊


