Given a root element and a space-separated chain of tag names like 'div span a', find every element matching the last tag in the chain that also has — somewhere above it — elements matching each earlier tag, in the same nesting order. This is the CSS descendant combinator (div p means "every p inside a div") reduced to tag names: the earlier tags must be ancestors of the candidate, but they need not be direct parents.
function getElementsByTagNameHierarchy(root: Element, selector: string): Element[];
selector is one or more tag names separated by whitespace, ordered ancestor to descendant. Matching is case-insensitive. Returns a plain array in document order; the root itself is never a candidate or an ancestor.
const root = document.createElement('div');
root.innerHTML = '<div><section><p>a</p></section></div>';
getElementsByTagNameHierarchy(root, 'div p');
// → [<p>a</p>] (the p has a div ancestor, even though section sits between)
root.innerHTML = '<div><p>a</p></div><section><p>b</p></section>';
getElementsByTagNameHierarchy(root, 'div p');
// → [<p>a</p>] (b has no div ancestor, so it is excluded)
root.innerHTML = '<ul><li><a>x</a></li></ul>';
getElementsByTagNameHierarchy(root, 'ul li a'); // → [<a>x</a>]
'div p' matches <div><section><p></p></section></div>.'p div' is a different query from 'div p' and generally returns different elements.'a b c', a candidate c needs a b ancestor that itself has an a ancestor — the tags must appear in that outward order, not merely all be present.root is never returned and never counts as one of the ancestors in the chain.'DIV P' and 'div p' behave identically; element tagName is upper-cased internally.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.