5

I want to target all descendant paragraphs of a certain class while ignoring all descendant paragraphs of another class inside the first (this should work no matter which class is inside which). To achieve this I had to use 4 selectors, like this:

* {
  margin: 0.2em 0;
  width: fit-content;
}

div {
  margin-left: 1em
}

/* == 4 selectors to achieve desired effect = */

.orange p {
  background: orange;
}

.cyan .orange p {
  background: orange;
}

.cyan p {
  background: cyan;
}

.orange .cyan p {
  background: cyan;
}
<div class="orange">
        <p>Orange</p>
        <div>
          <p>Orange</p>
          <div>
            <p>Orange</p>
            <div class="cyan">
              <p>Cyan</p>
              <div>
                <p>Cyan</p>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="cyan">
        <p>Cyan</p>
        <div>
          <p>Cyan</p>
          <div>
            <p>Cyan</p>
            <div class="orange">
              <p>Orange</p>
              <div>
                <p>Orange</p>
              </div>
            </div>
          </div>
        </div>
      </div>

The question is: Can this be achieved using only two selectors? [The order of these two selectors should be able to change without altering the effect.]

I have tried selectors like:

.orange:not(.cyan) p {
  background: orange;
}

.cyan:not(.orange) p {
  background: cyan;
}

but it doesn't target the last one well, for it is inheriting the style of the first. I am looking for two selectors that match these cases without any particular order in the style sheet.

VorganHaze
  • 1,887
  • 1
  • 12
  • 35
  • 1
    I don't think you can do this in two selectors or technically you can since you can do .orange p, .cyan .orange p { background: orange; } and .cyan p, .orange .cyan p { background: cyan; } which I believe you could count as two selectors –  Mar 08 '20 at 18:41
  • 1
    You might also want to take a look at [this question](https://stackoverflow.com/q/22259735/3233827). – ssc-hrep3 Mar 08 '20 at 18:44

2 Answers2

2

Here is a simple solution with CSS variables. Check the following question for more details: CSS scoped custom property ignored when used to calculate variable in outer scope

* {
  margin: 0.2em 0;
  width: fit-content;
}

div {
  margin-left: 1em
}

p {
  background: var(--c);
}
.cyan {
  --c:cyan;
}
.orange {
  --c:orange;
}
<div class="orange">
  <p>Orange</p>
  <div>
    <p>Orange</p>
    <div>
      <p>Orange</p>
      <div class="cyan">
        <p>Cyan</p>
        <div>
          <p>Cyan</p>
        </div>
      </div>
    </div>
  </div>
</div>
<div class="cyan">
  <p>Cyan</p>
  <div>
    <p>Cyan</p>
    <div>
      <p>Cyan</p>
      <div class="orange">
        <p>Orange</p>
        <div>
          <p>Orange</p>
        </div>
      </div>
    </div>
  </div>
</div>

You can scale it to any number of coloration as you only need one selector per color and the order doesn't matter:

* {
  margin: 0.2em 0;
  width: fit-content;
}

div {
  margin-left: 1em
}

p {
  background: var(--c);
}
.cyan {
  --c:cyan;
}
.orange {
  --c:orange;
}
.blue {
  --c:lightblue;
}
<div class="orange">
  <p>Orange</p>
  <div>
    <p>Orange</p>
    <div>
      <p>Orange</p>
      <div class="cyan">
        <p>Cyan</p>
        <div class="blue">
          <p>Blue</p>
        </div>
      </div>
    </div>
  </div>
</div>
<div class="cyan">
  <p>Cyan</p>
  <div class="blue">
    <p>Blue</p>
    <div>
      <p>Blue</p>
      <div class="orange">
        <p>Orange</p>
        <div>
          <p>Orange</p>
        </div>
      </div>
    </div>
  </div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • 1
    This is an incredible elegant solution! – VorganHaze Mar 08 '20 at 19:19
  • @VorganHaze this can work with any number of coloration too, check the update – Temani Afif Mar 08 '20 at 19:27
  • I mean, this is incredible, and I immediately saw the potential for so many other things. I have not thought of that solution before, and I am already testing it. The only reason I have not yet vote your answer as definitely is because I want to leave the answer "alive" for a little bit longer to see what others come with. But this is definitely an elegant and powerful solution. And if nothing better come soon, you will have your answer selected. Thank you for that solution. – VorganHaze Mar 08 '20 at 19:35
  • 1
    @VorganHaze yes no hurry, still to early to accept an answer especially a Sunday ;) – Temani Afif Mar 08 '20 at 19:36
0

You cannot achieve what you want, because this is not how CSS works. Both of your statement will have the same specificity, so CSS determines which rule will win according to the order in the CSS file. For the inner styles, you will need to have a statement which has a greater specificity. This can either be achieved with listing all combinations of classes or by e.g. using the child-selector (>).

I thought, I'd share an improvement to your solution. It still uses 4 CSS statements (for 2 colors) but it does not require you to write down all possible combinations (in case of more than 2 classes, it is less effort; see the example below).

First, you colorize any child <p> of a cyan element with the color cyan. Then, you overwrite this behavior with the child-selector which targets only direct children of your element. .orange > p then overwrites .cyan p. The same goes with orange/cyan.

* { font-family: sans-serif; }

.cyan p {
  background: cyan;
}
.orange p {
  background: orange;
}
.red p {
  background: red;
}

.cyan > p {
  background: cyan;
}
.orange > p {
  background: orange;
}
.red > p {
  background: red;
}
<ul>
  <li class="orange">
    <ul>
      <li class="cyan"><p>.orange >> .cyan</p></li>
      <li class="red"><p>.orange >> red</p></li>
      <li><p>.orange >> &ndash;</p></li>
    </ul>
  </li>
  <li class="cyan">
    <ul>
      <li class="orange"><p>.cyan >> .orange</p></li>
      <li class="red"><p>.cyan >> .red</p></li>
      <li><p>.cyan >> &ndash;</p></li>
    </ul>
  </li>
  <li class="red">
    <ul>
      <li class="orange"><p>red >> orange</p></li>
      <li class="cyan"><p>red >> cyan</p></li>
      <li><p>.red >> &ndash;</p></li>
    </ul>
  </li>
</ul>
ssc-hrep3
  • 15,024
  • 7
  • 48
  • 87
  • 1
    this wouldn't work in case the p is not a direct child, as OP posted I think they wanted the behaviour regardless if it is a direct child, that's why in the example there are divisions added within the inner classes –  Mar 08 '20 at 18:38
  • 1
    The inner element needs a CSS statement with a higher specificity. That's the only way it works. The example above shows such a solution. It's not possible if the two statements have the same specificity. – ssc-hrep3 Mar 08 '20 at 18:43
  • ok so > means what exactly, I tested your solution and I was wrong but could you explain to me if the following logic is right? If you have a

    markup. and you'd specify a selector div > p this wouldn't select the p because span is not a div, but if you'd have

    and used a .smt > p this would select the p because span inherited the class from the parent div?

    –  Mar 08 '20 at 18:55