44

I have an HTML list like so:

<ul>
  <li class="heading">Heading 1</li>
  <li class="heading">Heading 2</li>
  <li>Text Under Heading 2</li>
</ul>

Since Heading 1 has no text under it, I want to hide it with CSS.

If I do this,

li.heading + li.heading { display: none; }

it hides Heading 2 instead of Heading 1.

How can I hide Heading 1? Is there a way to look for adjacent siblings and select the first one?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Jeremy
  • 3,221
  • 7
  • 27
  • 31
  • 3
    @greut: If I can't find a CSS selector to do this, I will use Javascript. – Jeremy Jan 12 '12 at 00:38
  • 1
    Jeremy, I don't quite understand what you mean by "first adjacent sibling". Can't you just use `:first-child`? – BoltClock Jan 12 '12 at 05:43
  • 2
    on a side note, this HTML is poorly semantic. Why not use a heading tag to be a heading?? if you did this you may find the other issues would resolve themselves. – pgee70 Dec 16 '15 at 11:59

9 Answers9

25

It is possible to target first sibling with CSS, with some limitations.

For the example in the question it could be done with something like this

li.heading { display: none; }                   /* apply to all elements */
li.heading + li.heading { display: list-item }  /* override for all but first sibling */

This works, but requires that you can explicitly set the styles on the siblings to override the setting for first child.

  • But if your html is dynamic and sometimes you'll only have a single li.heading, it'll be hidden. – Codemonkey Jul 06 '22 at 10:44
  • The object of this was to hide the first element, so if there is only one element then that is the first one and will be hidden. Any way, this answer is getting close to a decade old and lots of things have changed in CSS and there have been many features added to it which make solving something like this better. – Eggert Jóhannesson Jul 07 '22 at 11:32
  • Can you tell me how you'd solve it better now? Thanks – Codemonkey Jul 11 '22 at 08:41
21

It is not possible using CSS as currently defined and implemented. It would require a selector that selects an element on the basis of its siblings after it. CSS selectors can select an element on the basis of preceeding or outer elements, but not on the basis of following or inner elements.

The desired effect can be achieved using JavaScript in a rather straightforward way, and you can decide, depending on the purpose, whether you just remove the elements from display or completely remove them from the document tree.

matthias_h
  • 11,356
  • 9
  • 22
  • 40
Jukka K. Korpela
  • 195,524
  • 37
  • 270
  • 390
  • 19
    Instead of resorting to JavaScript, it would probably make more sense to change the HTML structure so that it *can* be achieved with CSS... – Šime Vidas Jan 12 '12 at 00:42
  • 7
    @ŠimeVidas I just want to point out that there are situations where you don't have control over the HTML (for instance, reading HTML straight from the database - ugh...) but you do have control over the JavaScript. But I agree that it would be best if possible. – Pixel Elephant Sep 07 '12 at 23:10
6

This is now possible

li:first-of-type {
    display: none;
}

This will match the first li tag.

li:first-of-type:not(:only-of-type) {
    margin: 10px;
}

If you want a bit more control - such as adding space between two items only when there are more items the above would work. Mixing pseudo selectors can be very powerful. https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes

Ric
  • 3,195
  • 1
  • 33
  • 51
4

This is now possible using the :has() pseudo-class together with the adjacent sibling combinator (+).

The functional :has() CSS pseudo-class represents an element if any of the relative selectors that are passed as an argument match at least one element when anchored against this element. This pseudo-class presents a way of selecting a parent element or a previous sibling element with respect to a reference element by taking a relative selector list as an argument.

/* Selects an h1 heading with a
paragraph element that immediately follows
the h1 and applies the style to h1 */
h1:has(+ p) {
  margin-bottom: 0;
}

For your example you could use:

li.heading:has(+ li.heading) { display: none; }

Note that this pseudo-class is still very new and is not yet fully supported by all major browsers (e.g. Firefox).

n-peugnet
  • 41
  • 4
4

There are a few ways to hide only the "Heading 1" only:

ul li:first-child {display:none;}

Alternatively:

li.parent{ display: none; }
li.parent + li.parent { display: list-item; }

Also, <li>Child of Heading 2</li> is not a child of <li class="parent">Heading 2</li>. It is a sibling.

bookcasey
  • 39,223
  • 13
  • 77
  • 94
  • Neither of those solutions worked. In the first case, I might not want to hide the first child if there was a "Text Under Heading 1". The second case did not work when I tried it. It seems CSS does not support what I want to do. – Jeremy Jan 12 '12 at 00:44
  • The second solution offered here should work for your HTML if you replace "parent" with "header". – ale10ander Dec 17 '22 at 03:58
0

try using JS to getElementsby Classname and if more than 1 then use CSS:

ul li:first-child {display: none;}
TylerH
  • 20,799
  • 66
  • 75
  • 101
Nabeel Khan
  • 3,715
  • 2
  • 24
  • 37
0

Use :first-of-type

You can read more here

Inam Ul Huq
  • 732
  • 7
  • 8
0

The official CSS3 Spec does not currently support anything like this, though I do realize that it would be useful.

I would try doing searches for some pre-built JavaScript or jQuery scripts/libraries for adding CSS selectors. Although I have never come across anything.

If you do not find anything, you might as well just do it manually, or try finding a completely different solution.

TylerH
  • 20,799
  • 66
  • 75
  • 101
James Kyle
  • 4,138
  • 4
  • 28
  • 41
-1

I know that this question has already a valid marked answer, but maybe other people want to use my css-only solution:

I wanted to have a list of alerts (in this case bootstrap alerts) in a container and their border to collapse. Each alert has a border-radius, which looks rather silly when they are all in one container. So with margin-top: -1px I made their borders collapse. As a next step I had to modify the styles of the first, every alert in between and the last alert. And this should also look nice for a single alert, two alerts and n alerts.

.alert {
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    border-bottom-left-radius: 0px;
    border-bottom-right-radius: 0px;
    margin: 0;
}

// set for the first alert that it should have a rounded top border

.alert:last-child {
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
}

// set for the last alert that it should have a rounded bottom border
// if there is only one alert this will also trigger it to have a rounded bottom border aswell

.alert+.alert:last-child {
  margin-top: -1px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

//for the last border of n alerts we set the top border to be collapsing and remove only the the top rounded borders

.alert+.alert:not(:last-child) {
  margin-top: -1px;
  border-radius: 0;
}

// for each following alert in between we remove the top rounded borders and make their borders collapse

And here is an angular template for multiple alerts.

<div id="alertscontainer">
    <div data-ng-repeat="alert in data.alerts" class="alert" data-ng-class="'alert-' + alert.class">
        {{alert.body}}
    </div>
</div>
FTav
  • 389
  • 4
  • 13