18

I'm trying to find a CSS selector for an element that is the first child, taking any text nodes into account that might come before it (i.e. if any elements come before, possibly unwrapped text nodes, this is no longer considered the first child).

But it seems :first-child does not include text nodes, neither does :nth-child, etc.

This is where I'm at, but it's not working:

.red-if-not-first {
  color: red;
  font-weight: bold;
}

.red-if-not-first:first-child {
  color: green;
}
<p>
  Lorem ipsum. <span class="red-if-not-first">This should be red, not green, because some content comes before it.</span> Eum natus culpa officia a molestias, sed beatae aut in autem architecto iure repellat quam placeat, expedita maxime laborum necessitatibus repudiandae. Corrupti!
</p>

<p>
  <span class="red-if-not-first">This is rightly green, not red, because it's first bit of content in this paragraph.</span> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eum natus culpa officia a molestias, sed beatae aut in autem architecto iure repellat quam placeat, expedita maxime laborum necessitatibus repudiandae. Corrupti!
</p>

Unfortunately I have little control over the markup.

I'm aware this has been asked before, but that was 3 years ago, which is as good as a thousand years in front-end!

Community
  • 1
  • 1
Josh Harrison
  • 5,927
  • 1
  • 30
  • 44
  • 1
    Would you be open to JavaScript solutions? I don't believe this is possible in CSS – Jonathan Lam Feb 06 '17 at 18:02
  • I could manage it in JavaScript myself, but I was curious to see if there was a (preferable) CSS only way. I've searched high and low and only found "no", but they were all from several years ago. – Josh Harrison Feb 06 '17 at 18:03
  • If you want an automatic solution, it can only be done via javascript friend. – zsawaf Feb 06 '17 at 18:41

4 Answers4

6

If, for some strange reason, you can make do with only supporting Gecko, you can use-moz-first-node selector to do this.

https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-first-node

Skatox
  • 4,237
  • 12
  • 42
  • 47
WesJ
  • 99
  • 1
  • 1
5

One workaround could be to make use of the :empty pseudo class. You will need more markup though.

p .red-if-not-first {
  color: red;
  font-weight: bold;
}

p > :empty + .red-if-not-first {
  color: green;
}
<p>
  <span>Lorem ipsum.</span> <span class="red-if-not-first">This should be red, not green, because some content comes before it.</span> Eum natus culpa officia a molestias, sed beatae aut in autem architecto iure repellat quam placeat, expedita maxime laborum necessitatibus repudiandae. Corrupti!
</p>

<p>
  <span></span> <span class="red-if-not-first">This is rightly green, not red, because it's first bit of content in this paragraph.</span> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eum natus culpa officia a molestias, sed beatae aut in autem architecto iure repellat quam placeat, expedita maxime laborum necessitatibus repudiandae. Corrupti!
</p>
Mr Lister
  • 45,515
  • 15
  • 108
  • 150
  • Thanks for the idea. Yes I thought about injecting an empty span at the start of each paragraph so I could use the `+` combination as a way of selecting the first instance in each paragraph. However I would have to use JS for that due to CMS limitations, so was wondering if there was a pure CSS way yet. – Josh Harrison Feb 06 '17 at 19:53
  • Ah, just ran the code in the answer from @Serg Chernata and seems my idea would not work. Thank you for your example which shows the markup I'd need – Josh Harrison Feb 06 '17 at 19:55
3

In essence, you're asking if text can affect the styling of dom elements and the answer is - no, because text is not a dom element of it's own.

We can prove this with a simple experiment. Just add a marker element at the beginning of the paragraph and then use a sibling selector to override color. You'll see that this works in both cases, because text has no effect on surrounding dom flow.

For the record, I thought I was onto something by initially doing this marker experiment with ::before pseudo elements but they can't be used with sibling selectors either. Pseudo elements are not real elements and will have no effect on the relationships of actual dom tree.

.red-if-not-first {
  color: red;
  font-weight: bold;
}

.red-if-not-first:first-child {
  color: green;
}

.marker + span{
  color: red;
}
<p>
  <i class="marker"></i>
  Lorem ipsum. <span class="red-if-not-first">This should be red, not green, because some content comes before it.</span> Eum natus culpa officia a molestias, sed beatae aut in autem architecto iure repellat quam placeat, expedita maxime laborum necessitatibus repudiandae. Corrupti!
</p>

<p>
  <i class="marker"></i>
  <span class="red-if-not-first">This is rightly green, not red, because it's first bit of content in this paragraph.</span> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eum natus culpa officia a molestias, sed beatae aut in autem architecto iure repellat quam placeat, expedita maxime laborum necessitatibus repudiandae. Corrupti!
</p>
Serg Chernata
  • 12,280
  • 6
  • 32
  • 50
2

This is 2017. The answer is "No". There is no such CSS selector that can help you with this.

Tony Dinh
  • 6,668
  • 5
  • 39
  • 58
  • Markup is there for a reason. OP should do it the right way. – Tony Dinh Feb 06 '17 at 18:40
  • 1
    There is no "right" way to do this using only CSS. In order to target those elements, you'd have to resort to JavaScript, which is awfully inconvenient for such a simple requirement. This should really be incorporated into the W3C. – Brandon McConnell Mar 31 '21 at 16:15
  • 5
    In addition, markup should not need to be injected justfor distinguishing what is already apparent. A :first-node would be a great addition to the CSS spec. – Chaos Crafter Apr 27 '21 at 02:42