No Time Dad

A blog about web development written by a busy dad

Simplified Sign Up Form

I think I’ve always made forms more challenging than they need to be. Likely because I didn’t know any better and followed complicated examples from web frameworks. I realized that there is a lot that plain html forms can do out of the box if you apply the right attributes. Manually cramming in form validation through a web framework is frustrating, and you might still have to do it for certain edge cases, but the valuable lesson I have recently learned is that it is better lean on native form elements and their respective attributes first, then go back and add edge case logic as needed.

I think people often get too deep into their respective web framework’s approach to forms and forget that forms have been around for a long time, long before most web frameworks, and some problems the web framework is trying to solve with have already been solved. It can often feel like you have blinders on when you’re using a web framework. Once I started ignoring the approach most web frameworks took and instead went back to the basics of building a form in plain html I think my forms improved dramatically. I should also mention that it is important to follow the best practices for your web framework when it comes to how it wants you to implement forms. Otherwise, you could find yourself in even more of a mess than when you started.

Naively, I recently thought to myself - “Can I build a user registration form in just html and css without any JavaScript?“. Looking back on that question I now think to myself - “How are you even a full-stack developer?”, but that just goes to show how I have been blinded by web frameworks. Below is an example of a simple sign up form that only uses html and css. The only additional code that would be needed is an endpoint on a server somewhere to process the incoming data, but that is outside the scope of the frontend.

On the form demo below, all three of the input fields are required. If I were to click on the submit button before filling out a required field the browser would display a tooltip asking me to complete the field and the form would not be submitted. I did not need to add any code to display the tooltip or prevent the form submission. I simply had to add the required attribute to the input element.

<input ... required />

There are a lot of types available for input elements. Probably more than I need for any given form, especially a sign up form. For my form below, an email address is needed so I set the input type to email. The email type has built in validation that checks for the @ symbol and top-level domain (.com, .net, .dev, etc) if it exists. So, a@b would be valid and a@b.dev would also be valid. Something like a@ or a@b. would not be valid.

I also need users to enter a password when signing up, so I will use a password type field. The text entered in this field will be automatically masked. Its common to add extra validation on password fields to meet strength requirements. I can use the pattern attribute and define a regex for my password inputs.

Similar to the required attribute, clicking Submit before entering an valid email or an valid password would result in the browser displaying a tooltip to tell me about the issue(s). I should note that you wouldn’t see multiple tooltips at once. They’d be shown one after another as the validation issues are resolved.

<input ... type="email" required />
<input ... type="password" pattern="..." required />

It’s good to give visual feedback on the input field on focus. Having the tooltip show up after clicking Submit is nice, but it is annoying to have to go back to the specific field after moving away from it to click the button. Everyone has felt that pain before. There are some pseudo-classes that can help with this.

Basically, what I want is when I first click on a required field the border should turn red to indicate that this field is required. I also want the border to turn red if the value in the field is invalid. But, (and this is personal preference … possibly wrong personal preference) I only want the invalid red border to be displayed if the I’m focused on the input. Once I click off of it I want the border to change back to gray. To achieve that, I’ll use the :focus pseudo-class with :invalid, :valid, and :required pseudo-classes. That way I can apply custom styles to each input state.

input:focus:valid {
  outline: none;
  box-shadow: 0 0 0 0.1rem rgb(173, 216, 230, 0.7);
}

input:focus:invalid:required {
  outline: none;
  box-shadow: 0 0 0 0.1rem lightcoral;
}

I do need to consider whether I should mark required fields before the user clicks on them. Usually done with an asterisk or putting the words “required” on the label element text. From what I understand, having the required attribute on the input field is enough for most screen readers.

Another thing I need to consider if the confirm password field, which I conveniently glossed over. As far as I can tell, there isn’t a good way to validate this field prior to click Submit without using JavaScript. I think the easiest solution for this would be to just remove the field and trust the user to enter the password they intended to enter. I noticed that Stripe does this on their sign up form, so that must make it an okay thing to do…right?

I forced myself to dig into standard html forms to try to learn what was available out of the box. There isn’t anything wrong with using a web framework, but I just want to be cautious not to get too comfortable in the bubble it can create. In general, I really just hope this takes some of the pain of building forms away from me. Building forms is one of the worst parts of web development for me but it is slowly getting better.

Demo & code

<div class="Container">
  <div class="Card">
    <h1>Sign up</h1>
    <form>
      <div class="Field">
        <label for="email">Email</label>
        <input id="email" name="email" type="email" placeholder="Enter email address" required />
      </div>
      <div class="Field">
        <label htmlFor="password">Password</label>
        <input id="password" name="password" type="password" placeholder="Enter password" required />
      </div>
      <div class="Field">
        <label  htmlFor="confirm-password">Confirm Password</label>
        <input id="confirm-password" name="confirm-password" type="password" placeholder="Re-enter password" required />
      </div>
      <button type="submit">Submit</button>
    </form>
  </div>
</div>
.Container {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  background-color: whitesmoke;
}

.Card {
  padding: 2rem;
  border-radius: 0.5rem;
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  background-color: white;
}

.Field {
  display: flex;
  flex-direction: column;
  margin-bottom: 1.5rem;
}

label {
  margin-bottom: 0.25rem;
}

input {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
  overflow: visible;
  border-radius: 0.25rem;
  padding-bottom: 0.5rem;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
  padding-top: 0.5rem;
  border: 1px solid lightgray;
}

input:focus:invalid:required {
  outline: none;
  box-shadow: 0 0 0 0.1rem lightcoral;
}

input:focus:valid {
  outline: none;
  box-shadow: 0 0 0 0.1rem rgb(173, 216, 230, 0.7);
}

button {
  border-radius: 0.25rem;
  background-color: white;
  padding: 0.5rem 1.5rem;
  border: 1px solid lightgray;
}

button:hover {
  background-color: whitesmoke;
}

button:focus {
  outline: none;
  box-shadow: 0 0 0 0.1rem rgb(173, 216, 230, 0.7);
}