2

I have this structure in html

<div id="A">
   ....
   <div id="B">
      ....
   </div>
   ....
</div>

How can I write a CSS rule, that says, make all a tags color white inside #A, but ignore what's in #B?

I would prefer to have something like :not(#B) and not put another wrapper tag or anything too hardcoded.

Thanks

omega
  • 40,311
  • 81
  • 251
  • 474
  • Possible duplicate of [css selector for first direct child only](http://stackoverflow.com/questions/2094508/css-selector-for-first-direct-child-only) – Jeff.Clark Apr 08 '16 at 20:08
  • I don't want to restrict it to direct children, that's why I put `....`. – omega Apr 08 '16 at 20:11
  • Also, visit https://css-tricks.com/child-and-sibling-selectors/ for more fun stuff on child and sibling selectors. – Jeff.Clark Apr 08 '16 at 20:11
  • You need to put in a more complete example then. You say, "...make all a tags color white inside #A, but ignore what's in #B..." That could mean that you ONLY want links that are direct children of A, or it could mean all descendants of A, except anything in B. You must be explicit – Jeff.Clark Apr 08 '16 at 20:13

5 Answers5

7

Best solution (although still not perfext):

(Corrected after the comment and with the code of @Amit)

/* Either directly under #A, or in an element in #A that's not #B */
/* The element that's not #B must be a direct child of #A, otherwise */
/* children of children of #B will be selected anyway, as @Amit pointed out. */
#A > a, #A > :not(#B) a { color:red }
<div id="A">
   <a>red</a>
   <div id="B">
      <a>black</a>
      <p>
        <a>black</a>
      </p>
   </div>
   <p>
     <a>red</a>
   </p>
</div>

This still has problems (IE 9+ and not working if #B is wrapped), but it is the best solution we've got.

Incorrect, failing solution (just to show what's wrong):

#A > a, #A :not(#B) a { color:red }
<div id="A">
   <a>red</a>
   <div id="B">
      <a>black</a>
      <p>
        <a>black</a>
      </p>
   </div>
   <p>
     <a>red</a>
   </p>
</div>
LarsW
  • 1,514
  • 1
  • 13
  • 25
  • That's wrong. Both of them... The first guarantees nothing - if any formatting other then the `A` related rules exist, it will also be reverted, but worse only on supported browsers (not *all* browsers). The second isn't any better, see my answer fit details. – Amit Apr 08 '16 at 20:48
  • @Amit Ok, I see I've made some mistakes. The first one is what came to mind first, but is not the best answer. I'll move it down, with a warning. The second actually really wrong, thanks for pointing out. I'll edit it and credit you, because I of course, can't leave it as it is. If I've forgotten something, please tell me. – LarsW Apr 08 '16 at 20:55
  • @Amit Thank you again for pointing out, it's really silly for the top answer to be wrong. I hope I gave you enough credit. – LarsW Apr 08 '16 at 21:07
  • I appreciate the thankful remarks, but I still claim there is no right answer. Any suggestion will be limited that way or the other.(side note, you probably want to remove the incorrect solution altogether) – Amit Apr 08 '16 at 21:09
  • @Amit Yeah, that's a thing. I forgot to tell about problems with this solution. I did add some emphasize to the incorrect solution being wrong, but I think I'll leave it in to show what was wrong. – LarsW Apr 08 '16 at 21:13
  • A general solution for the selector would be #A > a, #A > :not(#B) a, #A > :not(#B) > :not(#B) a, #A > :not(#B) > :not(#B) > :not(#B) a .... write enough cases to cover all the posible depth levels in the DOM – vals Apr 09 '16 at 08:24
2

You’re on the right track with :not(#B) already.

You want to format the links that are direct children of #A, and those that are further down the tree, but not those in #B.

/* edited, was previously just #A > a, #A :not(#B) a, which won’t work for deeper nesting
   inside #B, as Amit pointed out */
#A > a, #A > :not(#B) a { color:green; }

/* for illustration purposes only */
#B { border:1px solid red; }
#B:before { content:"[I’m #B, my links aren’t green.]"; display:block; }
p { border:1px solid yellow; }
p:before { content:"[I’m a paragraph, the link inside me is not a child of #A.]"; display:block; }
<div id="A">
  <a href="#">Link</a>
  <div id="B">
    <a href="#">Link</a>
    <span><a href="#">Link inside span</a></span>              
  </div>
  <p>
    <a href="#">Link</a>
  </p>
</div>

Edit: As Amit pointed out, #A :not(#B) a would not work for links nested deeper into #B. So the :not(#B) part has to be a child of #A, #A > :not(#B) a. Example edited.

CBroe
  • 91,630
  • 14
  • 92
  • 150
  • Does this work, if it was the `a` tags not under #B, was wrapped in say span tags? I don't want to restrict it to direct children of #A. – omega Apr 08 '16 at 20:13
  • Yes, to demonstrate exactly that, I did include the link that is wrapped in the paragraph element – so it is not a child of #A. – CBroe Apr 08 '16 at 20:14
  • @Amit: Yeah, thanks for shamelessly copycatting my example code … ;p – CBroe Apr 08 '16 at 20:53
  • Are you sure you noticed the ;p …? – CBroe Apr 08 '16 at 20:57
  • @Amit: No worries :) I edited my answer in that regard, and credited you for pointing this out. I did consider the situation where the links _outside_ of #B would be nested deeper (

    ) in my example (which the first selector, `#A > a` of course would not catch), but did not think about what would happen for deeper nesting inside of #B. And you are totally right, `:not(#B)` on its own doesn’t work for that, because a span or anything else would of course also satisfy `:not(#B)`.
    – CBroe Apr 08 '16 at 21:03
  • Sorry if the “shamelessly copycatting” came off wrong, despite the smilie (you are of course correct, taking a posted example to suggest an improvement or correction is totally valid) … but so could “That's just wrong”, if you’re honest … could’ve perhaps phrased that more mildly, “you have overlooked a certain case/situation/condition.” – CBroe Apr 08 '16 at 21:06
  • The point is (and my answer explains this with more details), there is no real solution, just a bunch of "almosts". – Amit Apr 08 '16 at 21:07
  • Yeah I agree, and apologize for the aggressive wording – Amit Apr 08 '16 at 21:07
  • I think our “almost”, `#A > a, #A > :not(#B) a`, is good enough, it should cover what OP needs – as they said in the question, _“and not put another wrapper tag”_ – I think that means #B will always be a child of #A. – CBroe Apr 08 '16 at 21:10
2

Why not do simply:

#A a {
 color:#fff;
}
#B a {
 color:green;
}
stckvrw
  • 1,689
  • 18
  • 42
  • 1
    why not usually revolves around the higher specificity of ID selectors, which should generally be avoided. – zzzzBov Apr 08 '16 at 20:10
2

There's no solution that "just works" without restrictions. Your best effort would be to set explicit rules to elements within your negated selector (:not(#B)).

The reason for this is that rules are evaluated "positively", they look for a positive match, so for example (taken from one of the other "inaccurate" answers):

#A > a, #A :not(#B) a { color:green; }

/* for illustration purposes only */
#B { border:1px solid red; }
#B:before { content:"[I’m #B, my links aren’t green.]"; display:block; }
p { border:1px solid yellow; }
p:before { content:"[I’m a paragraph, the link inside me is not a child of #A.]"; display:block; }
<div id="A">
  <a href="#">Link</a>
  <div id="B">
    <span>
      <a href="#">I am green after all</a>
    </span>
  </div>
  <p>
    <a href="#">Link</a>
  </p>
</div>

The <span> around the link serves as a positive match for :not(#B), and the logic breaks.

Perhaps the closest you can get is by restricting matches the direct children plus nested children whose top most parent under A is not B:

#A > a, #A > :not(#B) a { color:green; }
<div id="A">
  <a href="#">Link</a>
  <div id="B">
    <span>
      <a href="#">I am really not green</a>
    </span>
  </div>
  <p>
    <a href="#">Link</a>
  </p>
</div>

But this would also break as soon as any element wraps B.

Amit
  • 45,440
  • 9
  • 78
  • 110
-1

If you are actually trying to target <a> tags that appear under these elements and had markup that looked like the following :

<div id="A">
    <a href='#'>Test A1</a>
       <div id="B">
          <a href='#'>Test B</a>
       </div>
     <a href='#'>Test A2</a>
</div>

You could take advantage of the direct descendant operator > in CSS to only target elements directly below #A and not within it's children :

#A > a {
  /* This will only target <a> elements that are beneath #A and not in #B */
  color: #FFF;
}

And example of this can be seen here and might look like :

enter image description here

Update

It looks like you don't want to just target <a> tags. If that is the case, you could probably generalize the previous statement by only targeting elements not in B under A :

#A > :not(#B) {
    color: #FFF;
}

Updating the example markup :

<div id="A">
    <a href='#'>Test A1</a>
    <div id="B">
        <a href='#'>Test B</a>
    </div>
    <div id="C">
        I'm in C
    </div>
    <a href='#'>Test A2</a>

still will work as expected :

enter image description here

Rion Williams
  • 74,820
  • 37
  • 200
  • 327