Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a CSS selector that works on nested divs and selects the closest match?

Imagine I have a dynamically rendered page where I use classes class1 and class2 on some divs which contain each other:

<div class="class1">
  ...
  <div class="class2">
    ...
    <div class="insider">

I also have these selectors in my CSS:

.class1 .insider { ... }
.class2 .insider { ... }

Now the problem is that both selectors match the above .insider and the one later in the CSS source code will win. How can I make the selector win that has the classN parent closest to the .insider.


Below is a snippet where I reproduced my problem, I also showed my expectations with inline styles. The .other and .another divs are there to show that the depth of .insider is not known, so the direct child selector (>) is out of the picture.

/* Layout: irrelevant to the issue */
.outer {
  height: 100px;
  width: 100px;
}
.inner {
  display: inline-block;
  height: 50px;
  width: 50px;
  margin: 25px 25px;
}
.insider {
  text-align: center;
}

/* Colors: the visual part that should be fixed */
.class1 {
  background-color: black;
}
.class2 {
  background-color: lightgray;
}
.class1 .insider {
  color: red;
}
.class2 .insider {
  color: green;
}
<table>
<tr><th>Title</th><th>Actual</th><th>Expected</th><th>Explanation</th></tr>
<tr>
<td>Inside class1-2</td>
<td>
  <div class="outer class1">
    <div class="inner class2">
      <div class="other">
        <p class="insider">asdf</p>
      </div>
    </div>
  </div>
</td>
<td>
  <div class="outer" style="background-color: black;">
    <div class="inner" style="background-color: lightgray;">
      <div class="other">
        <p class="insider" style="color: green;">asdf</p>
      </div>
    </div>
  </div>
</td>
<td>The text color should be green because the closest styled parent is "class2" and not because ".class2 .insider" is declared after ".class1 .insider" in the CSS code. Swap the red and green styles in CSS and the text turns red.</td>
</tr>
<tr>
<td>Inside class2-1</td>
<td>
  <div class="outer class2">
    <div class="inner class1">
      <div class="other">
        <div class="another">
          <p class="insider">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>
  <div class="outer" style="background-color: lightgray;">
    <div class="inner" style="background-color: black;">
      <div class="other">
        <div class="another">
          <p class="insider" style="color: red;">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>The text color should be red because the closest styled parent is "class1".</td>
</tr>
<tr>
<td>Inside class2-1-2</td>
<td>
  <div class="outer class2">
    <div class="inner class1">
      <div class="other class2">
        <div class="another">
          <p class="insider">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>
  <div class="outer" style="background-color: lightgray;">
    <div class="inner" style="background-color: black;">
      <div class="other" style="background-color: lightgray;">
        <div class="another">
          <p class="insider" style="color: green;">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>The text color should be green because the closest styled parent is "class2", and not because the outermost styled parent is "class2", nor because of declaration order, see the first case.</td>
</tr>
</table>

All of the examples show this in the Chrome Dev Tools Inspector: enter image description here
where it's visible that the green color wins because it has bigger line number.

like image 766
TWiStErRob Avatar asked Feb 07 '23 15:02

TWiStErRob


2 Answers

.class1 .insider,
.class2 .class1 .insider{
  color: red;
}
.class2 .insider,
.class1 .class2 .insider{
  color: green;
}

The downside of this solution is as your N increase the number of selector you have to write also increase recursively. But it can be helped with a pre-processing library like SASS or LESS.

Demo:

/* Layout: irrelevant to the issue */
.outer {
  height: 100px;
  width: 100px;
}
.inner {
  display: inline-block;
  height: 50px;
  width: 50px;
  margin: 25px 25px;
}
.insider {
  text-align: center;
}

/* Colors: the visual part that should be fixed */
.class1 {
  background-color: black;
}
.class2 {
  background-color: lightgray;
}
.class1 .insider,
.class2 .class1 .insider{
  color: red;
}
.class2 .insider,
.class1 .class2 .insider{
  color: green;
}
<table>
<tr><th>Title</th><th>Actual</th><th>Expected</th><th>Explanation</th></tr>
<tr>
<td>Inside class1-2</td>
<td>
  <div class="outer class1">
    <div class="inner class2">
      <div class="other">
        <p class="insider">asdf</p>
      </div>
    </div>
  </div>
</td>
<td>
  <div class="outer" style="background-color: black;">
    <div class="inner" style="background-color: lightgray;">
      <div class="other">
        <p class="insider" style="color: green;">asdf</p>
      </div>
    </div>
  </div>
</td>
<td>The text color should be green because the closest styled parent is "class2" and not because ".class2 .insider" is declared after ".class1 .insider" in the CSS code. Swap the red and green styles in CSS and the text turns red.</td>
</tr>
<tr>
<td>Inside class2-1</td>
<td>
  <div class="outer class2">
    <div class="inner class1">
      <div class="other">
        <div class="another">
          <p class="insider">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>
  <div class="outer" style="background-color: lightgray;">
    <div class="inner" style="background-color: black;">
      <div class="other">
        <div class="another">
          <p class="insider" style="color: red;">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>The text color should be red because the closest styled parent is "class1".</td>
</tr>
<tr>
<td>Inside class2-1-2</td>
<td>
  <div class="outer class2">
    <div class="inner class1">
      <div class="other class2">
        <div class="another">
          <p class="insider">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>
  <div class="outer" style="background-color: lightgray;">
    <div class="inner" style="background-color: black;">
      <div class="other" style="background-color: lightgray;">
        <div class="another">
          <p class="insider" style="color: green;">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>The text color should be green because the closest styled parent is "class2", and not because the outermost styled parent is "class2", nor because of declaration order, see the first case.</td>
</tr>
</table>

Edit with another N-independent version in which you only need to add selectors if your nested HTML layout get more complex:

.unique_class_name1 .insider,
[class~=unique_class_name] .unique_class_name1 .insider{
  color: red;
}
.unique_class_name2 .insider,
[class~=unique_class_name] .unique_class_name2 .insider{
  color: green;
}

In this version though, it is very important to make sure the pattern unique_class_name is truly unique, since class~=unique_class_name will hit all elements where the class attribute contains that key.

/* Layout: irrelevant to the issue */
.outer {
  height: 100px;
  width: 100px;
}
.inner {
  display: inline-block;
  height: 50px;
  width: 50px;
  margin: 25px 25px;
}
.insider {
  text-align: center;
}

/* Colors: the visual part that should be fixed */
.unique_class_name1 {
  background-color: black;
}
.unique_class_name2 {
  background-color: lightgray;
}
.unique_class_name1 .insider,
[class~=unique_class_name] .unique_class_name1 .insider{
  color: red;
}
.unique_class_name2 .insider,
[class~=unique_class_name] .unique_class_name2 .insider{
  color: green;
}
<table>
<tr><th>Title</th><th>Actual</th><th>Expected</th><th>Explanation</th></tr>
<tr>
<td>Inside class1-2</td>
<td>
  <div class="outer unique_class_name1">
    <div class="inner unique_class_name2">
      <div class="other">
        <p class="insider">asdf</p>
      </div>
    </div>
  </div>
</td>
<td>
  <div class="outer" style="background-color: black;">
    <div class="inner" style="background-color: lightgray;">
      <div class="other">
        <p class="insider" style="color: green;">asdf</p>
      </div>
    </div>
  </div>
</td>
<td>The text color should be green because the closest styled parent is "class2" and not because ".class2 .insider" is declared after ".class1 .insider" in the CSS code. Swap the red and green styles in CSS and the text turns red.</td>
</tr>
<tr>
<td>Inside class2-1</td>
<td>
  <div class="outer unique_class_name2">
    <div class="inner unique_class_name1">
      <div class="other">
        <div class="another">
          <p class="insider">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>
  <div class="outer" style="background-color: lightgray;">
    <div class="inner" style="background-color: black;">
      <div class="other">
        <div class="another">
          <p class="insider" style="color: red;">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>The text color should be red because the closest styled parent is "class1".</td>
</tr>
<tr>
<td>Inside class2-1-2</td>
<td>
  <div class="outer unique_class_name2">
    <div class="inner unique_class_name1">
      <div class="other unique_class_name2">
        <div class="another">
          <p class="insider">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>
  <div class="outer" style="background-color: lightgray;">
    <div class="inner" style="background-color: black;">
      <div class="other" style="background-color: lightgray;">
        <div class="another">
          <p class="insider" style="color: green;">asdf</p>
        </div>
      </div>
    </div>
  </div>
</td>
<td>The text color should be green because the closest styled parent is "class2", and not because the outermost styled parent is "class2", nor because of declaration order, see the first case.</td>
</tr>
</table>
like image 78
AVAVT Avatar answered Feb 16 '23 03:02

AVAVT


Have you tried specifying both rules for nesting and not nesting elements?

Like .c1 .c2 .insider, .c2 .insider { color: red;}

Other way would be to refactor your code.

.c2 .c1 .c3,
.c1 .c3 {
  color: green;

}

.c1 .c2 .c3,
.c2 .c3 {
  color: blue;

}
<div class="c1">
  <div class="c2">
    <div class="c3">C2 - blue</div>
  </div>
</div>
<div class="c2">
  <div class="c1">
    <div class="c3">C1 - green</div>
  </div>
</div>
<div class="c1">
  <div class="c3">C1 - green</div>
</div>
<div class="c2">
  <div class="c3">C2 - blue</div>
</div>
like image 43
Justinas Avatar answered Feb 16 '23 01:02

Justinas