skip to Main Content

I’ve stumbled upon this question about CSS selector. Which is to select the first occurrence of <span> but igoring its hierarchy. I know I could use JavaScript to document.querySelectorAll('span') to get the first DOM object and assign class to it. But is there a way to achieve it avoid using JavaScript?

The HTML structure is arbitrary, so selectors like div:first-child > p:first-of-type > div:first-of-type span:first-of-type is too specific. I want the selector works even when HTML structure is changed.

I have tried something like this, but I’m not sure why MISS ME 4 is also selected.

span:first-of-type:not(:has(span):nth-child(n+2 of :has(span)) span) {
  color: red;
  font-weight: bold;
}
<div>
  <h1>Select the first occurrence of span</h1>
  <p>
    <span>HIT ME</span>
    <div>
      <span>MISS ME 1</span>
      <span>MISS ME 2</span>
      <span>MISS ME 3</span>
    </div>
    <span>MISS ME 4</span>
    <span>MISS ME 5</span>
  </p>
  <p>
    <span>MISS ME 6</span>
    <span>MISS ME 7</span>
  </p>
</div>
<div>
  <p>
    <span>MISS ME 7</span>
    <span>MISS ME 8</span>
  </p>
  <p>
    <span>MISS ME 9</span>
    <span>MISS ME 10</span>
    <span>MISS ME 11</span>
  </p>
  <p>
    <span>MISS ME 12</span>
    <span>MISS ME 13</span>
    <span>MISS ME 14</span>
    <div>
      <span>MISS ME 15</span>
      <span>MISS ME 16</span>
      <span>MISS ME 17</span>
    </div>
  </p>
</div>

Is it even possible? I’m completely out of ideas.

2

Answers


  1. To select the first span in a document select the span element that:

    • is not preceded by a sibling span element
    • is not preceded by a sibling element that contains a span element
    • doesn’t have an ancestor that’s a span element
    • doesn’t have an ancestor that’s preceded by a sibling span element
    • doesn’t have an ancestor that’s preceded by a sibling element that contains a span element

    Use:

    span:not(span ~ span):not(:has(span) ~ span):not(span span):not(span ~ * span):not(:has(span) ~ * span)
    
    span:not(span ~ span):not(:has(span) ~ span):not(span span):not(span ~ * span):not(:has(span) ~ * span) {
      color: red;
      font-weight: bold;
    }
    <div>
      <h1>Select the first occurrence of span</h1>
      <p>
        <span>HIT ME</span>
        <div>
          <span>MISS ME 1</span>
          <span>MISS ME 2</span>
          <span>MISS ME 3</span>
        </div>
        <span>MISS ME 4</span>
        <span>MISS ME 5</span>
      </p>
      <p>
        <span>MISS ME 6</span>
        <span>MISS ME 7</span>
      </p>
    </div>
    <div>
      <p>
        <span>MISS ME 7</span>
        <span>MISS ME 8</span>
      </p>
      <p>
        <span>MISS ME 9</span>
        <span>MISS ME 10</span>
        <span>MISS ME 11</span>
      </p>
      <p>
        <span>MISS ME 12</span>
        <span>MISS ME 13</span>
        <span>MISS ME 14</span>
        <div>
          <span>MISS ME 15</span>
          <span>MISS ME 16</span>
          <span>MISS ME 17</span>
        </div>
      </p>
    </div>
    Login or Signup to reply.
  2. A more generic version that should work with classes as well:

    .target { /* update what you want to select here (class, tag, etc) */
      &:nth-child(1 of &):not(:has(&) ~ *):not(:has(&) ~ * *):not(& *) {
        border-color: red;
      }
    }
    
    div {
      padding-inline: 15px;
    }
    .target {
      border: 3px solid blue;
    }
    <div>Some content</div>
    <div class="target">
      <div>Some content</div>
      <div>
        <div class="target">Some content</div>
        <div>Some content</div>
        <div class="target">Some content</div>
      </div>
      <div>Some content</div>
      <div>Some content</div>
    </div>
    <div class="target">
      <div>Some content</div>
      <div>Some content</div>
    </di>
    <div>
      <div class="target">
        <div class="target">Some content</div>
        <div>Some content</div>
      </div>
      <div>
        <div>Some content</div>
        <div class="target">Some content</div>
        <div>Some content</div>
      </div>
      <div class="target">
        <div>Some content</div>
        <div class="target">Some content</div>
        <div>Some content
          <div>Some content</div>
          <div class="target">Some content</div>
        </div>
        <div>
          <div>
            <div>
              <div>Some content</div>
              <div>Some content</div>
              <div>Some content</div>
            </div>
          </div>
        </div>
        <div class="target">
          <div class="target">Some content</div>
          <div>Some content</div>
          <div>Some content</div>
        </div>
      </div>

    Applied to your example:

    span { 
      &:nth-child(1 of &):not(:has(&) ~ *):not(:has(&) ~ * *):not(& *) {
        color: red;
        font-weight: bold;
      }
    }
    <div>
      <h1>Select the first occurrence of span</h1>
      <p>
        <span>HIT ME</span>
        <span>
          <span>MISS ME 1</span>
          <span>MISS ME 2</span>
          <span>MISS ME 3</span>
        </span>
        <span>MISS ME 4</span>
        <span>MISS ME 5</span>
      </p>
      <p>
        <span>MISS ME 6</span>
        <span>MISS ME 7</span>
      </p>
    </div>
    <div>
      <p>
        <span>MISS ME 7</span>
        <span>MISS ME 8</span>
      </p>
      <p>
        <span>MISS ME 9</span>
        <span>MISS ME 10</span>
        <span>MISS ME 11</span>
      </p>
      <p>
        <span>MISS ME 12</span>
        <span>MISS ME 13</span>
        <span>MISS ME 14</span>
        <span>
          <span>MISS ME 15</span>
          <span>MISS ME 16</span>
          <span>MISS ME 17</span>
        </span>
      </p>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search