1

I have this example

https://jsfiddle.net/kohnbhg3/

<div id="foo">
    <div class="A">
        A
    </div>
    <div class="B">
        B
    </div>
    <div class="B">
        C
    </div>
    <div class="B">
        D
    </div>
</div>

css

#foo .B {
  margin:5px 0;
}
#foo .B:first-child {
  margin:40px 0 5px 0;
}
#foo .B:last-child {
  margin:5px 0 40px 0;
}

And I want the first child .B to have the margin top 40, and the last child .B to have margin bottom of 40, and in between margin 5 top and bottom.

However first-child seems to assume that the element must be the first child in the list overall. But right now it is only the first child that matched being .B.

Is there a css way of saying, first of the selected?

Does anyone know how I can fix this?

Thanks

omega
  • 40,311
  • 81
  • 251
  • 474
  • 2
    first-child simply says first of it's parent. Thats all it says. Not first matching a selector. This question gets raised on here all the time and it's probably one of the most common CSS misconceptions. – Robert Wade Nov 28 '17 at 00:33
  • 1
    There is no simple way to do this, but you can see this exact question asked [here](https://stackoverflow.com/questions/6447045/css3-selector-first-of-type-with-class-name) with a clever but imperfect solution. – Dylan Stark Nov 28 '17 at 00:33
  • *"Is there a css way of saying, first of the selected?"* No, but you can add class to selected items and use `.class:first-child` to select the first item with class. – P.S. Nov 28 '17 at 00:34
  • 1
    Possible duplicate of [CSS selector for first element with class](https://stackoverflow.com/questions/2717480/css-selector-for-first-element-with-class) – Robert Wade Nov 28 '17 at 01:09

2 Answers2

2

You can apply different rules to the first match of multiple siblings (using CSS) by setting the rule for all siblings and overriding it for subsequent ones:

#foo > .B {
  color: red;
}
#foo > .B ~ .B {
  color: black;
}
<div id="foo">
    <div class="A">
        A
    </div>
    <div class="B">
        B
    </div>
    <div class="B">
        C
    </div>
    <div class="B">
        D
    </div>
</div>

However, this is not possible for the last match, as CSS only parses forwards, never backwards.
But you can use JavaScript for it. Here's how:

let parentSelector = '#foo', childSelector = '.B', 
    parentElement = document.querySelector(parentSelector),
    firstChild = parentElement.querySelector(childSelector),
    lastChild = [].slice.call(parentElement.querySelectorAll(childSelector)).slice(-1)[0];

Test:

let parentSelector = '#foo', 
    childSelector = '.B', 
    parentElement = document.querySelector(parentSelector),
    firstChild = parentElement.querySelector(childSelector),
    lastChild = [].slice.call(parentElement.querySelectorAll(childSelector)).slice(-1)[0];

firstChild.style.color = 'red';
lastChild.style.color = 'orange';
<div id="foo">
    <div class="A">
        A
    </div>
    <div class="B">
        B
    </div>
    <div class="B">
        C
    </div>
    <div class="B">
        D
    </div>
    <div class="A">
        E
    </div>
</div>

You can take it further and apply classes to first and last elems instead of modifying styles on the fly, to increase maintainability, but that's a different subject altogether.
Keep in mind the above code only parses the first element matched by parentSelector. If you have more than one and want to parse all, it gets a bit more complex:

let parentSelector = '.parent', 
    childSelector = '.B',
    firstChild = (parent, childSelector) => parent.querySelector(childSelector),
    lastChild = (parent, childSelector) => [].slice.call(parent.querySelectorAll(childSelector)).slice(-1)[0],
    applyMyChanges = el => {
      firstChild(el, childSelector).style.color = 'red';
      lastChild(el, childSelector).style.color = 'orange'
    };

[].slice.call(document.querySelectorAll(parentSelector)).map( applyMyChanges );
<div class="parent">
    <div class="A">A</div>
    <div class="B">B</div>
    <div class="B">C</div>
    <div class="B">D</div>
    <div class="A">E</div>
</div>
<div class="parent">
    <div class="A">A</div>
    <div class="B">B</div>
    <div class="B">C</div>
    <div class="B">D</div>
    <div class="A">E</div>
</div>

jQuery syntax for this task is a tad more concise:

For one instance (with #id):

let parentSelector = '#foo',
    childSelector = '.B';
$(childSelector, parentSelector).first().css({color:'red'});
$(childSelector, parentSelector).last().css({color:'orange'});

For any number of instances (with .class):

let parentSelector = '.parent',
    childSelector = '.B';
$(parentSelector).each(function() {
  $(childSelector, this).first().css({color:'red'});
  $(childSelector, this).last().css({color:'orange'});
});

jQuery test:

let parentSelector = '.parent',
    childSelector = '.B';
$(parentSelector).each(function() {
  $(childSelector, this).first().css({color:'red'});
  $(childSelector, this).last().css({color:'orange'});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="parent">
    <div class="A">A</div>
    <div class="B">B</div>
    <div class="B">C</div>
    <div class="B">D</div>
    <div class="A">E</div>
</div>
<div class="parent">
    <div class="A">A</div>
    <div class="B">B</div>
    <div class="B">C</div>
    <div class="B">D</div>
    <div class="A">E</div>
</div>
tao
  • 82,996
  • 16
  • 114
  • 150
  • @DavidThomas, like I said, CSS is limited here, but JavaScript can save the day. – tao Nov 28 '17 at 01:02
  • @DavidThomas `:not(.B) + .B` will select all `.B` elements that are not immediately following a `.B` element, which is clearly not what OP asked for. If `.A` and `.B` alternate, all `.B` will be selected by your selector. – tao Nov 28 '17 at 01:05
  • Good points! :) And, in this case, I too would probably opt for a JavaScript solution. And now: to get sleep, to avoid more silly mistakes... ;) – David Thomas Nov 28 '17 at 01:08
0

Look like you can not do this, unless all your element with class red must be the same type in it's parent. Your code will work if you use :first-of-type for something like this:

<div id="foo">
    <div class="A">
        A
    </div>
    <p class="B">
        B
    </p>
    <p class="B">
        C
    </p>
    <p class="B">
        D
    </p>
</div>

There is a well explained for this problem here: CSS selector for first element with class

Hope you can get more info.

chiquyet
  • 527
  • 4
  • 14