tricky css selector
Tags: css, javascript, tutorial, html
Published: 2022-05-05 15:32:55

## 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


```html
<!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
``` 

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

```css
h2:first-of-type~p {color:blue}
h2:nth-of-type(2)~p {color:red}
h2:nth-of-type(3)~p {color:green}
```
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:

```css
h2:nth-of-type(1)~p:not(h2:nth-of-type(1)~*:not(p)~p)
``` 

and the css for coloring the paragraphs


```css
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;
}
``` 

## the problem toggle display

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

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

The rest is no longer a real problem because we already created the appropriate selectors in the previous step:  
  
```js
"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'))
      )
  )

```

The complete code:


```html
<!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>
```