What this project does
A useful signup form does more than reject an entry. It tells someone which field needs attention and how to fix it. In this project, you will create name, email, and password fields. Submitting an incomplete form will show a message beside each affected field and move focus to the first one. Once all three fields pass the checks, the page will display a confirmation that the form is ready to send.
You need a text editor and a browser that supports JavaScript ES2015 and standard HTML form controls; no framework, package manager, or server is required for this exercise. The example creates its own form with JavaScript, so you can run it in the browser console on a blank page. It does not create an account or transmit a password. For a real signup process, a server would need to validate the submitted data and handle it securely.
Create the fields and their message areas
Start by giving every input a visible label and a dedicated place for its error message. The code below creates the entire form. Copy it into the browser console as one block. The message areas start empty; each input points to its message area through aria-describedby. A small amount of spacing keeps the fields readable without requiring a stylesheet.
const form = document.createElement("form"); form.noValidate = true; const title = document.createElement("h2"); title.textContent = "Create an account"; form.append(title); const fields = {}; function addField(key, labelText, type, autocomplete) { const label = document.createElement("label"); label.textContent = labelText + " "; label.style.display = "block"; label.style.marginBottom = "1rem"; const input = document.createElement("input"); input.name = key; input.type = type; input.autocomplete = autocomplete; input.required = true; input.setAttribute("aria-describedby", key + "-error"); const error = document.createElement("span"); error.id = key + "-error"; error.style.display = "block"; error.setAttribute("aria-live", "polite"); label.append(input, error); form.append(label); fields[key] = { input, error }; } addField("name", "Name", "text", "name"); addField("email", "Email", "email", "email"); addField("password", "Password", "password", "new-password"); const button = document.createElement("button"); button.type = "submit"; button.textContent = "Sign up"; const status = document.createElement("p"); status.setAttribute("role", "status"); form.append(button, status); document.body.append(form);
The email field uses the browser’s email input type for a basic format check. The password field hides typed characters, but that alone does not protect a password after submission. The autocomplete values help browsers identify the intended fields; they do not change the validation rules.
Add specific, timely validation
Run this second block in the same page and console session. It checks for blank values, a basic email format, and a minimum password length of eight characters. The eight-character rule is an example requirement for this practice form, not a complete password policy. Notice that name and email are trimmed for the empty-value checks, while the password is left as entered.
let attemptedSubmit = false; function getMessages() { const name = fields.name.input; const email = fields.email.input; const password = fields.password.input; return { name: name.value.trim() ? "" : "Enter your name.", email: !email.value.trim() ? "Enter your email address." : email.validity.valid ? "" : "Enter an email address in a valid format.", password: !password.value ? "Enter a password." : password.value.length < 8 ? "Use at least 8 characters." : "" }; } function validate() { const messages = getMessages(); let firstInvalid = null; for (const [key, message] of Object.entries(messages)) { const { input, error } = fields[key]; error.textContent = message; input.setAttribute("aria-invalid", String(Boolean(message))); if (message && !firstInvalid) firstInvalid = input; } return firstInvalid; } form.addEventListener("submit", (event) => { event.preventDefault(); attemptedSubmit = true; status.textContent = ""; const firstInvalid = validate(); if (firstInvalid) { firstInvalid.focus(); } else { status.textContent = "Form ready to send; no account was created."; } }); form.addEventListener("input", () => { if (attemptedSubmit) validate(); status.textContent = ""; });
Setting noValidate on the form prevents the browser’s default validation popups from competing with these messages. Before the first submit attempt, the form stays quiet. Afterward, messages update as the person edits a field. The first invalid field receives focus on submit, and aria-invalid reflects whether each field currently has an error.
Try the form and consider its limits
Press Sign up with every field empty: each field should receive a distinct message, and focus should move to Name. Enter a name and submit again to check that the remaining messages still make sense. Try an email without an at sign, then correct it. Enter a short password and add characters until its message disappears. Finally, submit valid-looking values and check that the status says no account was created. If you refresh the page, run both code blocks again.
These checks improve feedback, but they cannot decide whether an email address belongs to someone or whether a password meets the needs of a real service. Browser checks can also be bypassed. In a production signup flow, the server must validate the submitted values independently and return useful errors when it rejects them. Keep those messages as specific as practical without revealing sensitive account details.



