Building a Robust Sign-In Form with Native Browser Features

This codelab walks through creating a sign-in form that is secure, accessible, and user-friendly, leveraging built-in browser capabilities. You'll learn how semantic HTML enables features like autofill, how to design for touchscreens, and how to implement custom validation.

You can follow along in Codepen or with local files. Focus on these skills:

  • Using semantic form elements to activate browser built-in functionality.
  • Creating a responsive form layout.
  • Applying practical form usability rules.

Starting with Meaningful HTML

Begin with elements crafted for this purpose: <form>, <section>, <label>, and <button>. These provide built-in browser behavior, accessibility support, and clearer markup.

Use this structure as your initial HTML:

<form action="#" method="post">
  <h1>Sign in</h1>
  <section>
    <label>Email</label>
    <input>
  </section>
  <section>
    <label>Password</label>
    <input>
  </section>
  <button>Sign in</button>
</form>

The default browser styling will be basic, so it will need custom CSS to be usable, especially on mobile.

Designing for Touchscreen and Responsive Layout

Optimize your form for a touch-first experience with appropriate spacing and font sizes. The key adjustments are in the sizing of inputs and buttons. A mobile-first approach with a media query ensures proper display across devices.

Critical checks at this stage on real hardware include:

  • Test readability of labels and inputs, aiding users with low vision.
  • Confirm input fields and the "Sign in" button are large enough for thumb touch targets.

Enabling Autofill and Password Managers

Use input attributes like id, name, type, and autocomplete to help browsers understand fields. This supports saving and filling in usernames and passwords securely.

Integrate the following attributes into your HTML:

<form action="#" method="post">
  <h1>Sign in</h1>
  <section>        
    <label for="email">Email</label>
    <input id="email" name="email" type="email" autocomplete="username" required autofocus>
  </section>
  <section id="password">
    <label for="password">Password</label>
    <input id="password" name="password" type="password" autocomplete="current-password" required>
  </section>
  <button id="sign-in">Sign in</button>
</form>

Key behaviors to verify:

  • Clicking a label focuses its linked input, and screen readers announce the label name.
  • Using type="email" prompts mobile keyboards for email input, showing the @ and . keys prominently.
  • Values entered in a type="password" field are hidden by default.
  • The autocomplete="username" attribute lets browsers propose stored emails for easy filling.

Different browsers employ varied heuristics to determine input roles and enable autofill. Testing across different browsers and devices is essential to ensure proper behavior.

The autocomplete="username" and autocomplete="current-password" attributes are key signals for secure autofill.

Creating a Password Visibility Toggle

Experts recommend allowing users to confirm the text they type. Because there's no native HTML feature for this, a small JavaScript implementation is needed. This example uses text rather than icons for clarity.

The implementation involves a few steps:

  1. Modify the password field's HTML to include a button:
  2. <section id="password">
      <label for="password">Password</label>
      <button id="toggle-password" type="button"
      aria-label="Show password as plain text. Warning: This displays your password on the screen.">
      Show password</button>
      <input id="password" name="password" type="password"
      autocomplete="current-password" required>
    </section>
    
  3. Add styling so the toggle button appears as inline text overlaid on the password input:
  4. button#toggle-password {
      background: none;
      border: none;
      cursor: pointer;
      font-weight: 300;
      padding: 0;
      position: absolute;
      top: -4px;
      right: -2px;
    }
    
  5. Add JavaScript that changes the input type and toggles the value of its aria-label:
  6. const passwordInput = document.getElementById('password');
    const togglePasswordButton = document.getElementById('toggle-password');
    
    togglePasswordButton.addEventListener('click', togglePassword);
    
    function togglePassword() {
      if (passwordInput.type === 'password') {
        passwordInput.type = 'text';
        togglePasswordButton.textContent = 'Hide password';
        togglePasswordButton.setAttribute('aria-label',
          'Hide password.');
      } else {
        passwordInput.type = 'password';
        togglePasswordButton.textContent = 'Show password';
        togglePasswordButton.setAttribute('aria-label',
          'Show password as plain text. ' +
          'Warning: this will display your password on the screen.');
      }
    }
    

This user interface pattern benefits from quick usability testing to see if users understand its function. Testing with assistive technology, like the ChromeVox Classic Extension, is a good way to audit its accessibility.

Implementing Client-Side Validation

Clear, immediate feedback helps prevent errors. HTML5 offers native validation for constraints, but JavaScript with the widely-supported Constraint Validation API provides more control for dynamic checks and integrated browser tooltips, letting you guide users without guessing.

  1. Start by informing users of password constraints and connecting them to the input using aria-describedby for screen readers:
  2. <section id="password">
      <label for="password">Password</label>
      <button id="toggle-password" type="button"
      aria-label="Show password as plain text. Warning: this will display your password on the screen.">
      Show password</button>
      <input id="password" name="password" type="password"
      autocomplete="current-password" aria-describedby="password-constraints" required>
      <div id="password-constraints">
        At least eight characters, with at least one lowercase and one uppercase letter.
      </div>
    </section>
    
  3. Add CSS to style the states that this validation will trigger:
  4. div#password-constraints {
      margin: 5px 0 0 0;
      font-size: 16px;
    }
    
  5. Finally, leverage the Constraint Validation API in JavaScript to validate during input and before submission:
  6. passwordInput.addEventListener('input', resetCustomValidity);
    function resetCustomValidity() {
      passwordInput.setCustomValidity('');
    }
    
    // A production site would use more stringent password testing.
    function validatePassword() {
      let message= '';
      if (!/.{8,}/.test(passwordInput.value)) {
        message = 'At least eight characters. ';
      }
      if (!/.*[A-Z].*/.test(passwordInput.value)) {
        message += 'At least one uppercase letter. ';
      }
      if (!/.*[a-z].*/.test(passwordInput.value)) {
        message += 'At least one lowercase letter.';
      }
      passwordInput.setCustomValidity(message);
    }
    
    const form = document.querySelector('form');
    const signinButton = document.querySelector('button#sign-in');
    
    form.addEventListener('submit', handleFormSubmission);
    
    function handleFormSubmission(event) {
      event.preventDefault();
      validatePassword();
      form.reportValidity();
      if (form.checkValidity() === false) {
      } else {
        // On a production site do form submission.
        alert('Logging in!')
        signinButton.disabled = 'true';
      }
    }
    

Test that the following scenarios are handled accurately: submitting an invalid email, submitting empty required fields, and entering password values that don't meet all criterion.

Next Steps for Your Form

With these fundamentals in place, you can build out the rest of your sign-in experience:

  • Include a Forgot your password? link to aid password recovery.
  • Add terms of service and privacy policy links at the start for data transparency.
  • Adjust the styling of these new elements to match your site's branding.
  • Implement performance and usability monitoring with RUM to measure the form's success in the real world.