A form can look simple and still be difficult to complete with a keyboard. A useful review follows the whole task: reach each field, understand its label, enter valid information, identify an error and continue after correction. This local HTML demonstration covers those steps for a small email-and-message form. It does not send or store a message.

Make each field understandable
Use a visible label associated with each input. Mark required fields in the label and with the HTML required attribute so people and browsers can identify the requirement. Use type="email" for an email field to provide the browser’s email input behavior. The W3C WAI forms tutorial describes built-in input validation and recommends clear instructions and accessible error notification.
In the example, each field also references its own error text with aria-describedby. When a submitted value is invalid, the script sets aria-invalid="true", writes a specific message beside the field and moves keyboard focus to that field. Once corrected, the next attempt clears the error state. The success message explicitly says the demonstration did not send or store anything.
Check the form with only a keyboard
- Reload the local file and press Tab. Confirm the email field receives a visible focus outline.
- Continue with Tab through the message field and submit button; use Shift+Tab to move backward.
- Submit an invalid email. Confirm focus returns to the email field and the nearby text explains the expected format.
- Correct the email, leave the message blank and submit. Confirm the message field receives focus and an error.
- Enter a message and submit. Confirm the status says no message was sent or stored.
The example uses a clearly visible :focus-visible outline and keeps focus order aligned with the visual order. The W3C’s guidance for form validation says errors should be identified in text and explains that native browser validation generally returns focus to an invalid field. The custom example makes its error copy and focus behavior explicit; review it against the WCAG 2.2 error identification guidance and the WAI form validation tutorial.
Keep client and server checks separate
Browser validation improves feedback, but users can bypass or alter client-side checks. The WAI tutorial explicitly recommends validating data on the server as well. A real form should repeat the required checks on the server, handle abuse safely, explain what data is stored and provide an appropriate privacy notice. This demonstration has no server and makes no accessibility conformance claim.
Open the local example
python3 -m http.server 8000 --directory examples
# Open http://127.0.0.1:8000/keyboard-form.htmlCopy the complete file below into keyboard-form.html in a new folder. In that folder, start the local server with the command above. The form runs only in your browser and its success text confirms that nothing was sent or stored.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Keyboard form practice — local example</title>
<style>
body { max-width: 38rem; margin: 2rem auto; padding: 1rem; font: 1rem/1.6 system-ui; color: #102033; }
label { display: block; margin-top: 1rem; font-weight: 600; }
input, textarea, button { font: inherit; padding: .6rem; }
input, textarea { box-sizing: border-box; width: 100%; }
button { margin-top: 1rem; }
:focus-visible { outline: 3px solid #124b9c; outline-offset: 3px; }
.error { color: #a01818; min-height: 1.5em; }
</style>
</head>
<body>
<h1>Try the form with a keyboard</h1>
<p>This is a local demonstration. It does not send or store a message.</p>
<form id="demo" novalidate>
<label for="email">Email address (required)</label>
<input id="email" type="email" autocomplete="email" required aria-describedby="email-error">
<p class="error" id="email-error"></p>
<label for="message">Message (required)</label>
<textarea id="message" rows="4" required aria-describedby="message-error"></textarea>
<p class="error" id="message-error"></p>
<button type="submit">Try submission</button>
</form>
<p id="result" role="status" tabindex="-1"></p>
<script type="module">
const form = document.querySelector('#demo');
const email = document.querySelector('#email');
const message = document.querySelector('#message');
const result = document.querySelector('#result');
form.addEventListener('submit', event => {
event.preventDefault();
result.textContent = '';
for (const field of [email, message]) {
field.removeAttribute('aria-invalid');
document.querySelector(`#${field.id}-error`).textContent = '';
}
if (!email.validity.valid) {
document.querySelector('#email-error').textContent = 'Enter an email address such as [email protected].';
email.setAttribute('aria-invalid', 'true');
email.focus();
return;
}
if (!message.value.trim()) {
document.querySelector('#message-error').textContent = 'Enter a message before continuing.';
message.setAttribute('aria-invalid', 'true');
message.focus();
return;
}
result.textContent = 'Demo complete: no message was sent or stored.';
result.focus();
});
</script>
</body>
</html>
For the broader recommendation and its companion resource, see the web accessibility course page. The short exercise here is a starting point for review, not a substitute for testing the complete site with assistive technologies and its intended users.
