I hate reading introductions, but I hate writing those even more. So, I’ll just cut shit to the point.

The setup

You have a form.

You want to collect data from it when the user submits it. To do this, you need to grab the reference to the form. You can use querySelector for this. Then, to get the value of the input, you can use another querySelector .

This method looks for the first element matching the CSS selector, starting from children of the element it was called upon.

And this works. But keep reading.

It’s a form for a reason

HTML is all about correct semantics. Utilizing the correct semantics not only makes your app more accessible to everyone, it also helps you while building it.

Why did we use the <form> tag here?

We could’ve bound a click event to the submit button. But what about submitting the form when pressing the enter key? We could bind that too. But what about mobile devices, where the keyboard’s “Enter” key changes to a helpful “next” button to enhance user’s experience?

We’re using the form because it is a form. Now let’s use that to our advantage even more. Instead of traversing the DOM, we can use the fact that we’re dealing with a form in our advantage.

The elements property

Since the browsers knows that it’s a form (you pretty spelled it out when you wrote that form tag), the browser already did the heavy lifting for you. It expects to find form fields in there, and it certainly does so. This happens while the DOM structure is created.

The form fields are stored in the elements property.

You also gave those form fields names (again, spelled it out: name attributes). Browsers are good at listening and remembering what you tell them. Use that to access the element you need.

The elements property contains other magical stuff, such as enumerated list of all form fields in the document order, which means you can iterate over it. But that’s a topic for another time.

The forms property

Forms are pretty important part of a webpage. I’m pretty sure that no two forms on a single page do the same thing. Which is why it’s a good thing to give them an id . Not a class . An id . It’s unique.

Now, instead of traversing the whole DOM looking for the form, you can use the forms property on the document object, which already has a list of all forms. Just like with elements on a single form, you can either iterate over all of them or access a single via its id .

And, ta-da! We got rid of all appearances of querySelector by using a more specific method which is better suited for out needs.

Think about the future

Until now, we’ve just been refactoring the code. Everything still works the same. The approach with using querySelector to get a value of a field works just fine. Yeah, maybe elements is a “more correct” way, but who cares… right?

There’s a catch, though: a form field doesn’t have to be contained inside the <form> element in the DOM. It’s just a default way of registering the field: it looks for the first form in the ancestors of a field. But there’s one more way to do it: there’s a form attribute.

The form attributes allows you match a form field with any form element from the document, by providing the form 's id as the value. Here, we have an external password field tied to the login form.

Yes, of course, we can query for this element with querySelector . But we have to perform this operation on the whole document, and we need to make sure that we’re getting the one tied with the correct form element.

Using the elements approach makes our code future-proof. If one day there’s another way to register the element, we don’t have to change our existing code: we don’t traverse the DOM ourselves, but utilize the power of semantic meaning of the document to grab the relevant fields.

Performance

When in a meeting and you need something smart to say, here’s a trick: put your fingers at your chin, look into the distance and ask: “but what about performance?” Everyone will look at you in awe thinking how you’re super thoughtful.

So how would I answer this if someone asked me? Easily: it’s actually a performance win. Doing querySelector goes around every node in the descendants and checks if it matches the selector.

By using the methods described in this article, you’re only iterating over a small percentage of nodes which are already fetched for you while the page was being built by the browser: it’s almost like doing a simple find in an array.

There’s no performance penalty: in fact, you’re actually using what the browser already offers you — as you should do whenever you can.

Disclaimer and conclusion

querySelector is a very powerful method and it has its place. It should be used when you want to traverse the DOM to locate a certain element. Try asking yourself this:

Do I really want to traverse the DOM? Or maybe I’m trying to guess something that browser should already know based on the semantics of this element?

I hope that you’ve learned something new today. There’s more gems like this in the DOM specification. I plan to cover a couple more cases soon.