Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

First Child + Not Selectors?

I have a dynamic list that has visible and hidden items:

<ul>
    <li class="hidden">
    <li class="hidden">
    <li>
</ul>

I want to apply style to the first element in the list that is not hidden.

My code for the first element:

ul li:first-child{
  font-size:10px;
}

My code to get the non hidden elements

ul li:not(.hidden){
  font-size:10px;
}

How can I merge the two in a cross browser solution? Ideally it would be something like:

ul li:not(.hidden):first-child

(which doesn't work)

like image 648
sigmaxf Avatar asked Aug 15 '15 05:08

sigmaxf


2 Answers

CSS only approach

First child does not work that way. You can read more about it in BoltClock's answer here. It selects the first-child of the parent (that also matches any additional conditions) and cannot select the first element among the children which match the provided condition.

Instead what you could do is first apply the required properties on all li:not(.hidden) elements and then override it with default settings for li:not(.hidden) ~ li:not(.hidden). The second selector means that any .hidden element which is a sibling of another (meaning it is not the first) would get the default setting whereas the first would get the modified setting (red color in the snippet).

The general sibling selector should be used as the adjacent sibling selector (+) may not help you completely because as you can see in the below snippet, it would only select all other elements as long as there is no other .hidden in between.

ul > li:not(.hidden) {
  color: red;
}
ul#one > li:not(.hidden) ~ li:not(.hidden) {
  color: black;
}
ul#two > li:not(.hidden) + li:not(.hidden) { /* wont help if any other hidden elements in between */
  color: black;
}
<ul id='one'>
  <li class="hidden">First hidden</li>
  <li class="hidden">Second hidden</li>
  <li>First not hidden</li>
  <li>Second not hidden</li>
  <li class="hidden">Third hidden</li>
  <li>Third not hidden</li>
</ul>

<ul id='two'>
  <li class="hidden">First hidden</li>
  <li class="hidden">Second hidden</li>
  <li>First not hidden</li>
  <li>Second not hidden</li>
  <li class="hidden">Third hidden</li>
  <li>Third not hidden</li>
</ul>

Using JavaScript/jQuery

I know you have asked for pure CSS selectors but as can been seen from both existing answers, it is not possible to achieve this without doing the override for adjacent siblings. If you want a straight-forward way of handling things then you could look at using JS or jQuery. Below are a couple of sample snippet:

jQuery:

$(document).ready(function() {
  $('ul').each(function() {
    $(this).children('li:not(.hidden):first').css('color', 'red');
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id='one'>
  <li class="hidden">First hidden</li>
  <li class="hidden">Second hidden</li>
  <li>First not hidden</li>
  <li>Second not hidden</li>
  <li class="hidden">Third hidden</li>
  <li>Third not hidden</li>
</ul>

<ul id='two'>
  <li>First not hidden</li>
  <li class="hidden">First hidden</li>
  <li class="hidden">Second hidden</li>
  <li>Second not hidden</li>
  <li>Third not hidden</li>
  <li class="hidden">Third hidden</li>
  <li>Fourth not hidden</li>
</ul>

<ul id='two'>
  <li class="hidden">First hidden</li>
  <li>First not hidden</li>
  <li class="hidden">Second hidden</li>
  <li>Second not hidden</li>
  <li>Third not hidden</li>
  <li class="hidden">Third hidden</li>
  <li>Fourth not hidden</li>
</ul>

JavaScript:

$(document).ready(function() {
  var ulEls = document.querySelectorAll('ul');
  for (var i = 0; i < ulEls.length; i++) {
    ulEls[i].querySelector('li:not(.hidden)').setAttribute('style', 'color: red');
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id='one'>
  <li class="hidden">First hidden</li>
  <li class="hidden">Second hidden</li>
  <li>First not hidden</li>
  <li>Second not hidden</li>
  <li class="hidden">Third hidden</li>
  <li>Third not hidden</li>
</ul>

<ul id='two'>
  <li>First not hidden</li>
  <li class="hidden">First hidden</li>
  <li class="hidden">Second hidden</li>
  <li>Second not hidden</li>
  <li>Third not hidden</li>
  <li class="hidden">Third hidden</li>
  <li>Fourth not hidden</li>
</ul>

<ul id='two'>
  <li class="hidden">First hidden</li>
  <li>First not hidden</li>
  <li class="hidden">Second hidden</li>
  <li>Second not hidden</li>
  <li>Third not hidden</li>
  <li class="hidden">Third hidden</li>
  <li>Fourth not hidden</li>
</ul>

Note I have used .each() function or loops in the above snippets because I wanted to illustrate how it covers multiple cases but you may not need them depending on your usage.

like image 148
Harry Avatar answered Sep 29 '22 13:09

Harry


:first-child matches with regards to the parent, not the first matched element. You can achieve the same thing with sibling selector, but you have to specify the base because there's no way of matching just once across the whole list:

ul > li:not(.hidden):first-child,
ul > li.hidden + li:not(.hidden) {
  font-size: 10px;
}
ul > li:not(.hidden) ~ li.hidden + li:not(.hidden) {
  font-size: 1em;
}
<ul>
    <li>1
    <li class="hidden">2
    <li>3
    <li>4
</ul>
<ul>
    <li class="hidden">1
    <li class="hidden">2
    <li>3
    <li>4
</ul>

If not for that :not there would've been a useful a CSS4 future feature nth-match where we would be able to do:

li:nth-match(1 of :not(.hidden)) {
  font-size: 10px;
}

But it will likely not allow nesting :not for performance reasons.

like image 32
Kit Sunde Avatar answered Sep 29 '22 12:09

Kit Sunde