Finding the Closest Element Through Shadow Roots

A great trick to find the closest element in the DOM that matches an arbitrary selector is Element.closest().

// Starts at el and walks the DOM until it finds a parent element
// that matches the selector. In this case, it will return the
// <body> element.
el.closest('body');

But what happens if you do this from inside a custom element's shadow root?

someElementInShadowRoot.closest('body');

By design, Element.closest() will not break out of the shadow root, so null is returned.

In my case, I needed to determine the lang of the closest element, even if the element was outside of a shadow root. Time for some recursive magic! ✨

Here's a TypeScript function that will do just that, even if the root is buried in multiple layers of shadow roots.

function closest(selector: string, root: Element = this) {
  function getNext(el: Element | HTMLElement, next = el && el.closest(selector)): Element | null {
    if (el instanceof Window || el instanceof Document || !el) {
      return null;
    }

    return next ? next : getNext((el.getRootNode() as ShadowRoot).host);
  }

  return getNext(root);
}

You can use it like this:

// Matches the closest element with a lang attribute, even if
// it's outside of the shadow root
const closestEl = closest('[lang]', el);