107

This is driving me nuts:

HTML:

<div><h1>Hello World!</h1></div>

CSS:

*:not(div) h1 { color: #900; }

Doesn't this read, "Select all h1 elements that have an ancestor that is not a div element...?" Thus, "Hello World!" should not be coloured red, yet it still is.

For the above markup, adding the child combinator works:

*:not(div) > h1 { color: #900; }

But doesn't affect the h1 element if it is not a child of a div element. For example:

<div><article><h1>Hello World!</h1></article></div>

Which is why I'd like to indicate the h1 element as a descendant, not a child, of the div element. Anyone?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
charles
  • 1,814
  • 2
  • 13
  • 19
  • 7
    On a side note, the moment you use any other kind of selector, you can instantly drop the `*`, i.e. `:not(div)`, just like how you specify `.class` or `#id` without the `*`. – BoltClock Aug 16 '11 at 20:00

2 Answers2

126

Doesn't this read, "Select all h1 elements that have an ancestor that is not a div element...?"

It does. But in a typical HTML document, every h1 has at least two ancestors that are not div elements — and those ancestors are none other than body and html.

This is the problem with trying to filter ancestors using :not(): it just doesn't work reliably, especially when the :not() is not being qualified by some other selector such as a type selector or a class selector, e.g. .foo:not(div). You'll have a much easier time simply applying styles to all h1 elements and overriding them with div h1.

In Selectors 4, :not() has been enhanced to accept full complex selectors containing combinators, including the descendant combinator. Whether this will be implemented in the fast profile (and thus CSS) remains to be tested and confirmed, but once it is implemented, then you will be able to use it to exclude elements with certain ancestors. Due to how selectors work, the negation has to be done on the element itself and not the ancestor in order to work reliably, and therefore the syntax will look a little different:

h1:not(div h1) { color: #900; }

Anyone who's familiar with jQuery will quickly point out that this selector works in jQuery today. This is one of a number of disparities between Selector 3's :not() and jQuery's :not(), which Selectors 4 seeks to rectify.

Community
  • 1
  • 1
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
20

The <html> element is not a <div>. The <body> element is not a <div>.

So the condition "has an ancestor that is not a <div>" will be true for all elements.

Unless you can use the > (child) selector, I don't think you can do what you're trying to do - it doesn't really make sense. In your second example, <article> is not a div, so that matches *:not(div) too.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
RichieHindle
  • 272,464
  • 47
  • 358
  • 399