1

This must be a very simple question for HTML ninjas out there, but I feel I'm missing something obvious here. Here is a snippet:

#red span {
  color: red;
}

#green span {
  color: green;
}
<div id="red">
  <p><span>red</span></p>
  <div id="green">
    <p><span>green</span></p>
  </div>
</div>

If I swap the stylesheet order, all of the text becomes red:

#green span {
  color: green;
}

#red span {
  color: red;
}
<div id="red">
  <p><span>red</span></p>
  <div id="green">
    <p><span>green</span></p>
  </div>
</div>

This happens despite the fact that <div id="green"> is a more inner parent of <span>green</span> than <div id="red"> in the DOM tree. I suppose it doesn't take precedence simple because its CSS now appears first in the order of stylesheets. So the order of stylesheets is what matters here.

Is this an expected behavior? Is this implementation/browser specific? Is there some official specs detailing that?

Finally, is there any CSS selector syntax I can use to make it work as in the first snippet, without relying on the order of stylesheets or adding new class names, ids, etc?

avo
  • 10,101
  • 13
  • 53
  • 81

3 Answers3

1

You can use > selector to make it apply to only specific span inside the div with the id you give and not all the span inside the div

#green > span {
  color: green;
}

 span {
  color: red;
}
<div id="red">
  <p><span>red</span></p>
  <div id="green">
    <span>green</span>
  </div>
</div>
ellipsis
  • 12,049
  • 2
  • 17
  • 33
  • This only works for direct descendants, doesn't it? What if I have `

    green

    `, or more nested tags?
    – avo Feb 07 '19 at 03:26
  • Not a good solution, but you can make all span red in color and then apply green to the desired span – ellipsis Feb 07 '19 at 03:36
  • so it doesn't really matter here what element is a more direct parent in the nesting hierarchy? Is it browser specific, perhaps? Or could you refer me to some docs? Thanks. – avo Feb 07 '19 at 03:39
  • It depends on how you want to use it. If you want to target one element, or if you want to target all specific element inside that element. Depending on position descendant operator can be use of nth child can be used. As far as i know it is not browser specific. You can refer this link https://www.bitdegree.org/learn/css-child-selector – ellipsis Feb 07 '19 at 03:52
1

Yes, the result you got is absolutely expected—well, maybe not expected, but they are correct. Here are the official specs. And here’s a tweet poll of mine detailing the exact same problem. (Spoiler: the majority of voters got it wrong.) Read the replies for a more in-depth discussion.

Currently, there’s not any CSS technology that takes “closest parent” scope into account. And this is a common misconception a lot of programmers have. (CSS is not a programming language.) A typical programmer will think, “The selector #red span means wherever I see a #red, look for a span inside, and then apply the styles. Since #green span is inside the #red, the green will apply after the red.” This is simply incorrect.

The way CSS actually applies styles is that it looks at each element, then goes through the stylesheets from top to bottom, decides if it matches, and then applies/overrides styles as it goes. That’s just one aspect of the cascade, among others (such as inheritance and specificity). Since in your second example #red span comes last in the CSS source, it gets applied last, overriding #green span, regardless of “how close” the span is within the #red in the DOM.

To fix your specific problem, the easiest thing to do is use a direct child selector, like #red > p > span and #green > p > span. But as you’d suspect, these selectors would have to be updated if you ever change the HTML. Coupling your CSS and HTML is a hassle, especially as your project grows.

The best strategy is not to depend on the DOM to style your elements. What happens when you move the span outside the #red? Would you want it to keep its style? For maintainable and scalable CSS, you should use classes only (not IDs) and apply the class to the actual element you want styled, without depending on DOM structure or parent-child relationships. That way, when your HTML structure changes, you don’t have to adjust your CSS to match.

Example:

.red {
  color: red;
}
.green {
  color: green;
}
<div>
  <p><span class="red">red</span></p>
  <div>
    <p><span class="green">green</span></p>
  </div>
</div>
chharvey
  • 8,580
  • 9
  • 56
  • 95
  • This is excellent and deserves a bounty. One thing though, in a prior edition of your answer you included [a very useful link](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Cascade_and_inheritance) which tought me about the `!important` modifier. That could be a very easy (albeit conceptually wrong) cure to my original issue: **`#green span { color: green !important; }`**. Maybe you can include that in your answer for completeness. – avo Feb 07 '19 at 05:05
  • 1
    thank you! i don’t recommend using `!important` though, because you will set yourself up for failure later down the road, having to override your own code (or someone else’s). it should really only be used for testing, quick hacks, and meeting tight deadlines. – chharvey Feb 07 '19 at 05:08
-1

If you want to do it to all descendants, you can achieve it by Javascript like following, it decreases many line of codes if you want to apply many colors, but will be slower time than css. It's up to your preferences

var colors = ['red', 'green', 'blue', 'orange', 'brown']
var spans = document.querySelectorAll('span');

spans.forEach(function(spanElement) {
  colors.forEach(function(color){
   if(spanElement.closest(`#${color}`)){
     spanElement.style.color=color
   }      
  }) 
})
<div id="red">
  <span>red</span>
  <div id="green">
    <span>green</span>
    <p><span>green</span></p>
  </div>
  <div id="blue">
    <span>blue</span>   
  </div>
  <div id="orange">
    <span>orange</span>   
  </div>
  <div id="brown">
    <span>brown</span>   
  </div>
</div>
Alvin Theodora
  • 936
  • 1
  • 11
  • 18
  • This only works for direct descendants, doesn't it? What if I have `

    green

    `, or more nested tags?
    – avo Feb 07 '19 at 03:28
  • Answer updated, you could achieve it by Javascript instead of css selector – Alvin Theodora Feb 07 '19 at 03:40
  • Thanks for the javascript, I think I'd rather stick with controlling the order of the CSS definitions. I'd accept your answer if you could also address my questions outlined in bold. – avo Feb 07 '19 at 03:48
  • Regarding the bold, it's because css is cascading means it reads from top to bottom. If you do greeen first then red, the green will be overwritten by the red. `#red span` means Select and style every `` element that is inside `#red` elements: – Alvin Theodora Feb 07 '19 at 03:52
  • 1
    Please don’t use javascript to do the job that CSS was made for. JS is slow, heavy, and resource-consuming. Do yourself—and your users—a favor by learning CSS. – chharvey Feb 07 '19 at 04:28
  • This is an alternative assuming author knows about class selector, but decided to not using class selector – Alvin Theodora Feb 07 '19 at 04:36
  • @AlvinTheodora, imho a better alternative would be `!important`, see my comment to the accepted answer. – avo Feb 07 '19 at 05:08
  • For short-term alternative, yes. For long-term and best-practice it's not the best solution as @chharvey reply. You can take a look at `!important` argument in [https://stackoverflow.com/questions/9245353/what-does-important-mean-in-css](https://stackoverflow.com/questions/9245353/what-does-important-mean-in-css) – Alvin Theodora Feb 07 '19 at 05:15