Use the .matches() Method to Determine if a Selector Matches an Element

A Superior, Native Alternative to the Lame Approaches I’ve Used in the Past

-

I’m rather late to the party, but I recently ran across JavaScript’s Element.matches(), which allows you to check if a given CSS selector matches an element. Put another way, it tells you if an element would be selected if the DOM were being queried by something like document.querySelector().

Why I Needed It

TypeIt offers a .move() method that allows someone to move the cursor through a string to a certain selector (any valid CSS selector in the HTML). As a part of that somewhat complex process, I’m searching an array of nodes for the index that has this selector. Again, because I can’t know what the selector is, I couldn’t reach for something like .classList.contains('my-selector'). At a super zoomed-in level, I’m instead doing a version of this:

const nodeIndex = allNodes.findIndex(n => n.matches('.selector'));

It’s a little bit of a face-palm moment, because although it’s supported in browsers going back to IE9, I’ve been using less-than-ideal techniques used in the past. This is post was written to honor them before saying goodbye forever.

Using Element.closest()

Using .closest() allows you to query up through the DOM tree rather than down, and it starts that query with the element on which it’s called. So, if the node itself matches that selector, you’ll get a truthy result. But this approach requires you to verify that the node you’ve found is the same node you’re interested in checking. Otherwise, you’ll still get a truthy result when .closest() matches against a parent element that happens to have the same selector.

Wrapping up this functionality might look like so:

function hasSelector(element: HTMLElement, selector: string): boolean {
  const matchedNode = node.closest(selector);

  if (!matchedNode) {
    return false;
  }

  return matchedNode === element;
}

There’s another downside worth noting. Because .closest() will continue traversing up the tree when it doesn’t find a match, you’re taking on some performance baggage. After all, you’re only actually interested in checking one element, but .closest() will keep searching all the way up the tree if it hasn’t found a match.

Checking the Parent’s Children for Same Node

You can’t use .querySelector() on an element to check itself for a selector, but you can check each of the parent’s children for it. If any of those matched elements are the same node as the target, you’ve got what you need.

function hasSelector(element: HTMLElement, selector: string): boolean {
	if (!element.parentElement) {
		return false;
	}

	return Array.from(element.parentElement.querySelectorAll(selector)).some(
		(node) => node === element
	);
}

In terms of performance, this approach is better than the previous because the maximum amount of DOM you’ll crawl is the target element’s direct parent. But readability arguably suffers, and depending on your DOM tree, you still carry some performance overhead because you’re evaluating multiple sibling elements, even though you’re concerned with only one.

Welcome to the 21st Century

With the .matches() method available, neither performance nor verbosity are problems anymore. Only one element — the target element — is ever being handled at a time. On top of that, the API is extremely clear. I’m still kicking myself that this thing has been around for years (and even longer if you consider the browser-specific implementations that existed before that). Hope you find it as useful as I have!


Was this post helpful? Thanks for the feedback!

Get an email whenever I publish a new post or project.

I won't send you spam. Unsubscribe whenever.
my face

Alex MacArthur is a software developer working for Dave Ramsey in Nashville, TN. Soli Deo gloria.

Leave a Comment

1 comments
  • Max
    This may result in something more useful and precise for me in the future, but up until now what’s worked pretty well for me has been the use of the ClassList API, which lets includes a “contains” method so you could do something like element.classList.contains(“some-class”)

    Won’t be as efficient as .matches() in all scenarios, but worth adding to the list of alternative methods

    https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
    1 reply
    • Alex
      Hi, Max - great callout. I should've noted that in my case, I don't know what that selector might be. It could be a class, data attribute, ID, etc. So, .matches() is the better tool for the job here.