13

I've got something that behaves the same way as the provided snippet. I've got a list of items, that when one is selected, I want that one to be displayed at the top of the list, which I can easily do with flex ordering. Only one can be selected at a time. But since each of the list elements have borders, the last element shouldn't have a border. Simply using the li:last-of-type works fine for most cases, except for when the it's the last list item that is selected.

I've tried several selectors that should select the last list item in a unordered list that doesn't have a given class, but the last-of-type selector doesn't seem to be behaving properly.

In case it isn't clear in the code, the selector I'm referring to is .selection li:not(.selected):last-of-type. And the problem is the double border at the bottom of the list. It should be a single border coming from the unordered list element.

.selection ul {
  display: flex;
  flex-direction: column;
  border: .15rem solid #666;
}

.selection li {
  order: 2;
  border-bottom: .15rem dashed #666;
}

.selection li.selected {
  order: 1;
}

/** This selector should work in my mind, but it doesn't **/
.selection li:not(.selected):last-of-type {
  border-bottom: none;
}

/** Non-important styles to put it into context **/
ul { list-style: none; padding: 0; width: 25%; margin: 2rem auto; }
li { padding: 1rem 2rem; background-color: #ebebeb; }
li:hover { background-color: #ccc; }
a { color: inherit; text-decoration: none; }
<div class="selection">
  <ul>
    <li><a href="">1</a></li>
    <li><a href="">2</a></li>
    <li class="selected">3</li>
  </ul>
</div>

I've also created this Codepen snippet to demonstrate the problem.

I have a feeling this is a problem with the :last-of-type selector, and maybe I'm using it wrong, but in my mind, the above selector should work.

Any help or insight into how to fix this, or another way to select the element, or insight into why this doesn't work would be appreciated.

Pavlin
  • 5,390
  • 6
  • 38
  • 51

3 Answers3

6

Consider this alternative approach:

  • Get rid of the :not selector entirely
  • Give the ul a border only on the left, right and top
  • Apply the bottom border only to the li

ul {
    display: flex;
    flex-direction: column;
    border-top: .15rem solid #666;
    border-left: .15rem solid #666;
    border-right: .15rem solid #666;
}

li { border-bottom: .15rem solid #666; }

Revised Codepen


Here's why your selector isn't working:

.selection li:not(.selected):last-of-type { border-bottom: none; }

It appears to me that you're saying you want to match the last li element as long as it doesn't have the class selected.

But this isn't how last-of-type works. If the li is a sibling, the selector will match it, regardless of class. The selector matches DOM elements. This means that even if you applied display: none to an li, the selector would still match it.

6.6.5.9. :last-of-type pseudo-class

The :last-of-type pseudo-class represents an element that is the last sibling of its type in the list of children of its parent element.

source: https://drafts.csswg.org/selectors-3/#last-of-type-pseudo

More details: How to get nth-child selector to skip hidden divs

Community
  • 1
  • 1
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • It would also be nice if I could have a different border on my list elements than on the general outside border, as I demonstrate in the revised snippet. This approach doesn't work in that scenario. – Pavlin Feb 17 '16 at 16:07
  • 1
    Thank you for the explanation, it seems I understood the selector wrong. I thought the `:not` selector would create a new context from which the `:last-of-type` selector would start off. It's clear to me now that this isn't the case at all. – Pavlin Feb 17 '16 at 16:35
2

Instead of trying to remove border-bottom from the last unselected item (which is hard or impossible in CSS), it's way easier to set a border-top to each item and remove the extra one from the item visually above others, which is... .selected!
Your constraint of having:

a different border on my list elements than on the general outside border

is permitted with this solution.

Updated pen

.selection ul {
  display: flex;
  flex-direction: column;
  border: .15rem solid #666;
}

.selection li {
  order: 2;
  border-top: .15rem dashed #666;
  background: lightblue;
}

.selection li.selected {
  order: 1;
  border-top: none;
  background: tomato;
}

/** Non-important styles to put it into context **/
ul { list-style: none; padding: 0; width: 25%; margin: 2rem auto; }
li { padding: 1rem 2rem; background-color: #ebebeb; }
li:hover { background-color: #ccc; }
a { color: inherit; text-decoration: none; }
<div class="selection">
  <ul>
    <li><a href="">1</a></li>
    <li><a href="">2</a></li>
    <li class="selected">3</li>
  </ul>
</div>
FelipeAls
  • 21,711
  • 8
  • 54
  • 74
  • This is perfect actually. I feel stupid for not thinking of it now ^^. Thank you, this does meet all the requirements. – Pavlin Feb 17 '16 at 16:31
2

Consider this more as a hack than an answer.

Just to show that it can be, in some way, done in CSS

the yellow element is the one selected.

.selection ul {
  display: flex;
  flex-direction: column;
  border: .15rem solid #666;
}

.selection li {
  order: 2;
  border-bottom: .15rem dashed #666;
}

div.selection li.selected {
  order: 1;
  background-color: yellow;
}

.selection li:nth-last-child(2) {
  order: 3;
}

.selection li:not(.selected):nth-last-child(2) {
  border-bottom: none;
}

.selection li.selected:nth-last-child(2) ~ li {
  border-bottom: none;
}


/** Non-important styles to put it into context **/
ul { list-style: none; padding: 0; width: 25%; margin: 2rem auto; }
li { padding: 1rem 2rem; background-color: #ebebeb; }
li:hover { background-color: #ccc; }
a { color: inherit; text-decoration: none; }
.selection {display: inline-block; width: 160px;}
<div class="selection">
  <ul>
    <li><a href="">1</a></li>
    <li><a href="">3</a></li>
    <li class="selected">2</li>
  </ul>
</div>
<div class="selection">
  <ul>
    <li><a href="">1</a></li>
    <li><a href="">3</a></li>
    <li><a href="">2</a></li>
  </ul>
</div>
<div class="selection">
  <ul>
    <li><a href="">1</a></li>
    <li class="selected"><a href="">3</a></li>
    <li><a href="">2</a></li>
  </ul>
</div>
vals
  • 61,425
  • 11
  • 89
  • 138