37

I have some nested elements like this:

<div class="foo">
    <div class="select-inside-this">
        <div class="not-inside-this">
            <div class="one select-this"></div>
        </div>
    </div>
</div>
<div class="select-inside-this">
    <div class="two select-this"></div>
</div>
<div class="three select-this"></div>

I want to select all .select-this which are inside .select-inside-this but not those which are wrapped in .not-inside-this. So in the end, i should be able to select only two.select-this from the above code. The CSS I've tried but did not work:

.select-inside-this :not(.not-inside-this) .select-this {
    /* style here /*
}

or:

.select-inside-this *:not(.not-inside-this) .select-this {
    /* style here /*
}

Any workaround here?

I don't want to use JavaScript here. I need pure CSS3 solution.

EDIT: I don't want to use direct child (>) selector. As I've asked, I want to select all those element from any level just without the exception wrapper.

Ethan
  • 3,410
  • 1
  • 28
  • 49
Sohan Zaman
  • 581
  • 1
  • 5
  • 13
  • The reason you can not use `.select-inside-this :not(.not-inside-this) .select-this` to select the required `.two.select-this` element here, is that `:not(.not-inside-this)` still needs to match an element for the whole selector to match (just _any_ element that does _not_ posses a certain characteristic) – but there is no element whatsoever between `.select-inside-this` and `.select-this` that could satisfy this, and therefor the whole selector _can not match_ this element. – CBroe Jul 07 '15 at 13:00
  • That makes sense. But why `.select-inside-this *:not(.not-inside-this) .select-this doesn't` work? – Sohan Zaman Jul 07 '15 at 13:44
  • Because it is basically the same thing. `*:not()` or `:not()`, that makes no difference whatsoever, both select _any_ element that does _not_ have the specified characteristic. – CBroe Jul 07 '15 at 14:09

4 Answers4

36

:not(.not-inside-this) and *:not(.not-inside-this) with the * are equivalent; in the case of the former, the universal selector is implied. See the spec.

It is currently not possible to construct a CSS selector that matches elements that are not descendants of specific elements for the reasons given in the following questions:

The selector

.select-inside-this :not(.not-inside-this) .select-this

matches .select-this elements that are descendants of some element that is not .not-inside-this, which in turn is a descendant of .select-inside-this. It does not match .select-this elements that are not descendants of .not-inside-this within .select-inside-this.

This means, first off, that your selector will incorrectly match the following:

<div class="select-inside-this">
    <div class="bar">
        <div class="not-inside-this">
            <div class="select-this"></div>
        </div>
    </div>
</div>

... because one of the ancestors of .select-this, .bar, is :not(.not-inside-this).

Additionally, this implies at least three levels of nesting (though it could be more). In your example, there are no other elements between .two.select-this and its containing .select-inside-this, so it will never match that element. This is why James Donnelly suggests adding .select-inside-this > .select-this to account for that particular case.

However it is still not possible to write a single complex selector using descendant combinators to match elements without a specific ancestor. The only way is to repeat the child combinator method with as many :not(.not-inside-this) as necessary, but this requires that you account for all possible cases. If you can't do that, then you're out of luck with CSS selectors.

Community
  • 1
  • 1
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • 6
    @Abram: My answer does not describe a solution to begin with, so I'm not quite understanding what you're referring to as not working. – BoltClock Aug 11 '18 at 02:48
8

You can use the Child Combinator Selector > to specify direct children:

.select-inside-this :not(.not-inside-this) .select-this,
.select-inside-this > .select-this {
    /* style here /*
}

This selects any .select-this element which is not a descendent of any .not-inside-this element and also selects .select-this elements which are direct children of .select-inside-this elements.

body > .select-inside-this :not(.not-inside-this) .select-this,
body > .select-inside-this > .select-this {
  color: red;
}
<div class="foo">
    <div class="select-inside-this">
        <div class="not-inside-this">
            <div class="one select-this">
                This should not be selected
            </div>
        </div>
    </div>
</div>
<div class="select-inside-this">
    <div class="two select-this">
        This should be selected
    </div>
</div>
<div class="three select-this">
    This should not be selected
</div>
James Donnelly
  • 126,410
  • 34
  • 208
  • 218
  • I know that I could do that. But what I really need is to select from ancestor. That's why I've asked the question ;) – Sohan Zaman Jul 07 '15 at 12:24
4

A little bit late to the party, and it might not match your use case, but this is what I ended up doing:

HTML:

<div class="foo">
    <div class="select-inside-this">
        <div class="not-inside-this">
            <div class="one select-this"></div>
        </div>
    </div>
</div>
<div class="select-inside-this">
    <div class="two select-this"></div>
</div>
<div class="three select-this"></div>

CSS:

.select-inside-this .select-this {
    background: blue;
}
.select-inside-this .not-inside-this .select-this {
    background: none;
}

The trick is to positively select the negative element and just undo the style.

It'll work for simple use cases, at the very least.

snazzybouche
  • 2,241
  • 3
  • 21
  • 51
0

I ended up

  1. styling but hiding the styles by default, and then
  2. revealing them on the nested element only.

Example with image backgrounds:

.box{
  height:100px;
  background-image: url("img.jpg");
  background-repeat: no-repeat;
  background-position: top 100px left 0; /*hide by default (here by shifting position)*/ 
}
.container .box{
  background-position: top left; /*reveal in the nested*/
}

Hope you find a way to hide the style you need in place in a similar way.

Fanky
  • 1,673
  • 1
  • 18
  • 20