Is there a CSS selector equivalent to JS querySelector?

Viewed 154

EDIT: This question is along a similar vein to my own, but it's not quite a duplicate because I understand how the pseudo-selectors of :first-child and :first-of-type actually work. The question is not "Why are my selectors not working," but rather "What selector besides just plain :first-child can I use for my circumstance?" The accepted answer of that question just explains information I already know.

===

In JavaScript, when you use element.querySelector('a'), it will return the first <a> tag it finds inside the element, no matter how deep, and it will not continue searching for more elements.

In CSS, you can use a space like .element a, and it will apply to every single <a> tag it finds inside the element, no matter how deep. This is more similar to element.querySelectorAll('a') if you were to compare it to JavaScript.

Even .element > a applies to every <a> tag that is a direct child of the element, still not quite right and I'd rather not depend on the nesting level. .element a:first-child is closer, but still not perfect. This will still apply to multiple elements if your structure looks like this:

<span class="element">
  <span>
    <a>Demo</a> <!-- I only want to select this one -->
    <a>Demo</a>
  </span>
  <span>
    <a>Demo</a>
    <a>Demo</a>
  </span>
</span>

In the example above, you could hypothetically use .element > span:first-child > a:first-child, but that becomes extremely rigid and heavily depends on the nesting structure, which in my case changes depending on the place it appears in the document. Normally I would adjust the HTML to have a more consistent and easier structure, but I don't have that freedom at this time, I just need to work with what's available.

Is there some kind of CSS magic I can use to select only the first match, regardless of depth? Or will I just plain need additional classes?

1 Answers

There is not a single selector that will cover that.

So you have pretty much two choices, either add a class, or use this trick, where you CAN apply styling in a way that will visually appear to affect only the element(s) you care about.

The idea is simply to get it half-right with the first selector .element a:first-of-type and then override/restore the ones that were incorrectly matched with a second selector .element :not(:first-child) a - this will also match <a> tags that were not first-of-type, so it seems like a good place to put your default styling along with .element a, to cover the rest.

.element {
  display: block;
}

/* first <a> tags to appear at any level turn blue */
.element a:first-of-type {
  color: blue;
}

/* "restore" styling on <a> tags with any parent that isn't the first child */
/* also, set default styling in one place */
.element :not(:first-child) a,
.element a {
  color: black;
}
<span class="element">
  <span>
    <a>Demo</a> <!-- Select this one -->
    <a>Demo</a>
  </span>
  <span>
    <a>Demo</a>
    <a>Demo</a>
  </span>
</span>

<span class="element">
  <span>
    <span>
      <a>Demo</a> <!-- Select this one, further nested -->
      <a>Demo</a>
    </span>
  </span>
  <span>
    <a>Demo</a>
    <a>Demo</a>
  </span>
</span>

Related