DEV Community

Cover image for tricky css selector
Frank Wisniewski
Frank Wisniewski

Posted on

tricky css selector

alternative to jQuerys nextUntil()

Today I encountered a seemingly simple problem:

  • the paragraphs under each header h2 need a different text color
    • sample1-1 to sample1-3 -> blue
    • sample2-1 to sample2-2 -> red
    • sample3-1 -> green
  • a click on h2 must toggle display the direct following paragraphs
<!DOCTYPE html>
<html lang=en>
  <meta charset=UTF-8>
  <title>Document</title>
  <h1>Sample</h1>
    <h2>sample-1</h2>
      <p>sample1-1
      <p>sample1-2
      <p>sample1-3
    <h2>sample-2</h2>
      <p>sample2-1
      <p>sample2-2
    <h2>sample-3</h2>
      <p>sample3-1
  <h3>Note</h3>
  <p>the paragraphs under each header2
  <p>need a different text color 
  <p>test1-1 to test1-3 -> blue
  <p>test2-1 to test2-2 -> red
  <p>test3-1 -> green
  <p>a click on h2 must toggle display the direct
  <p>following paragraphs
Enter fullscreen mode Exit fullscreen mode

OK, let's start with the colors,
and write some simple css rules,
the General Sibling Selector (~)
is my friend :-):

h2:first-of-type~p {color:blue}
h2:nth-of-type(2)~p {color:red}
h2:nth-of-type(3)~p {color:green}
Enter fullscreen mode Exit fullscreen mode

Done, the colors under the respective h2 are correct. Unfortunately, the paragraphs under the following h3 also turn green.
I didn't consider that the General Sibling Selector (~) selects all following paragraphs.

What should I do?

  • i need the first h2 Element h2:nth-of-type(1)
  • all the following paragraphs ~p
  • but not the following paragraphs behind the second h2

OK, at first i try to select all following paragraphs after the second h2

  • select all behind the first h2 h2:nth-of-type(1)~*
  • but not p (this selects only the headers h2,h2,h3)
  • but i need the following paragraphs with ~p
  • summarized h2:nth-of-type(1)~*:not(p)~p

So the complete selector looks like this:

h2:nth-of-type(1)~p:not(h2:nth-of-type(1)~*:not(p)~p)
Enter fullscreen mode Exit fullscreen mode

and the css for coloring the paragraphs

h2:nth-of-type(1)~p:not(h2:nth-of-type(1)~*:not(p)~p){
  color:blue;
}
h2:nth-of-type(2)~p:not(h2:nth-of-type(2)~*:not(p)~p){
  color:red;
}
h2:nth-of-type(3)~p:not(h2:nth-of-type(3)~*:not(p)~p){
  color:green;
}
Enter fullscreen mode Exit fullscreen mode

the problem toggle display

give h2 a pointer cursor and create a class to toggle display

h2{
  cursor: pointer;
  user-select:none;
}
.no-display{
  display:none;
}
Enter fullscreen mode Exit fullscreen mode

The rest is no longer a real problem because we already created the appropriate selectors in the previous step:

"use strict";

  document.querySelectorAll('h2')
    .forEach(( node, index ) =>
      node.addEventListener ( 'click', () =>
        document.querySelectorAll(
        `h2:nth-of-type(${index+1})~p:not(h2:nth-of-type(${index+1})~*:not(p)~p)`
        )
          .forEach(node => node.classList.toggle('no-display'))
      )
  )

Enter fullscreen mode Exit fullscreen mode

The complete code:

<!DOCTYPE html>
<html lang=de>
  <meta charset=UTF-8>
  <title>Document</title>
  <style>

    h2:nth-of-type(1)~p:not(h2:nth-of-type(1)~*:not(p)~p){
      color:blue;
    }
    h2:nth-of-type(2)~p:not(h2:nth-of-type(2)~*:not(p)~p){
      color:red;
    }
    h2:nth-of-type(3)~p:not(h2:nth-of-type(3)~*:not(p)~p){
      color:green;
    }

    h2{
      cursor: pointer;
      user-select:none;
    }
    .no-display{
      display:none;
    }

  </style>
  <h1>Sample</h1>
    <h2>Test-1</h2>
      <p>test1-1
      <p>test1-2
      <p>test1-3
    <h2>Test-2</h2>
      <p>test2-1
      <p>test2-2
    <h2>Test-3</h2>
      <p>test3-1
  <h3>Note</h3>
  <p>the paragraphs under each header2
  <p>need a different text color 
  <p>test1-1 to test1-3 -> blue
  <p>test2-1 to test2-2 -> red
  <p>test3-1 -> green
  <p>a click on h2 must toggle display the direct
  <p>following paragraphs

  <script>
  "use strict";

  document.querySelectorAll('h2')
    .forEach(( node, index ) =>
      node.addEventListener ( 'click', () =>
        document.querySelectorAll(
        `h2:nth-of-type(${index+1})~p:not(h2:nth-of-type(${index+1})~*:not(p)~p)`
        )
          .forEach(node => node.classList.toggle('no-display'))
      )
  )

  </script>
Enter fullscreen mode Exit fullscreen mode

Discussion (0)