How do I avoid `<style>` tags getting caught in my CSS selectors?

Viewed 162

I'm using CSS to style some existing documents, and using a lot of adjacent sibling selectors (e.g. item1 + item2) to apply spacing, etc. between elements. However, many of the documents also include <style> tags at the top of the page, or scattered throughout where custom styling has been applied. The custom styling itself is fine, but unfortunately the <style> tags themselves are interfering with my CSS selectors. For example, if I want to apply a margin-top to every element except the first, I would usually use * + * (aka, the "lobotomized owl" approach), and this works great UNTIL somebody puts a style tag at the top. Now the style tag is being read as the first element, and so EVERY element on the page is being selected. I do not know the element types in advance; they can be any combination of valid HTML code (div, span, p, table, etc...) and need to account for nested elements t as well. The key selector I am trying to fix is the adjacent sibling selector starting with a wildcard ( * + item).

Using something like :not(style) + item was my first thought, but then if there are any <style> tags in the middle of the page somewhere, any element after one of those will be styled incorrectly as well.

Is there a surefire way of doing this purely with CSS? I am not able to edit the HTML files themselves, but might be able to pre-process them with Javascript before they are rendered if I have to.

Edit: Bonus question, how do I select the first element in the page that is not a <style> tag? I.e, how do I now do body > *:first-child without selecting a <style> tag?

For example, my CSS file and then a target HTML:

div {
  background-color: yellow;
}

*+div {
  background-color: lime;
  margin-top: 1em;
}
<style>
  div {
    font-weight: bold;
  }
</style>

<div>Yellow</div>
<div>Green</div>
<div>Green</div>
<div>Green</div>
<div>Green</div>

1 Answers

Margins can get complicated as document layouts get more complex, but for simplicity's sake this answer will assume you only have to worry about lobotomized owls, and documents with relatively simple block layouts in which margins collapse between parents and children, so we can focus on the selectors at hand.

The following selector is robust to any number of element types and any nesting depth (i.e. not just children of body). Unfortunately it does involve repeating the same compound selector:

div, p {
  background-color: yellow;
  margin: 0; /* Remove default p margins for the sake of illustration */
}

:not(style):nth-of-type(n+2),
:not(style):nth-of-type(n+2) ~ * {
  background-color: lime;
  margin-top: 1em;
}

/* Ignore .nest elements for the sake of illustration */
.nest {
  background-color: transparent !important;
}
<style>
  div {
    font-weight: bold;
  }
</style>

<div>Yellow</div>
<div>Green</div>
<p>Green</p>

<style>
  p {
    font-style: italic;
  }
</style>

<div>Green</div>
<p>Green</p>

<section class="nest">
  <div>Yellow</div>
  <div>Green</div>
  <p>Green</p>
</section>

Bonus question, how do I select the first element in the page that is not a <style> tag? I.e, how do I now do body > *:first-child without selecting a <style> tag?

Assuming there will never be more than one style element before the first non-style child:

body > :not(style):first-child, body > style:first-child + *

If you think there may be documents with more than one consecutive style as the first children of body, you'll need to use this technique instead:

body > :not(style) {
  /* Apply styles to the first non-style child */
}

body > :not(style) ~ :not(style) {
  /* Undo the above styles for following non-style children */
}

There isn't a selector-based alternative that's robust until browsers start implementing level 4 :nth-child(An+B of S):

/* Replaces the main answer's entire selector-list */
:nth-child(n+2 of :not(body, style))

/* Replaces the bonus question's answer */
body > :nth-child(1 of :not(style))
Related