2

I have a generic layout component that needs to lay out its children with a vertical space between them:

.container > * + * {
  margin-top: 1rem;
}

For reasons I won't go into, I can't guarantee the order the component styles are loaded in.

If a child component has had a reset to its margins applied, for example …

.child {
  margin: 0
}

… and it is loaded after the .container css, its styled will win out because a wildcard selector has no specificity, meaning both declarations are of equal weight (so the last declaration will win).

I don't want the container to know or care about what its children are (and I don't want to add a specific class to all the children).

Is there any way to increase the specificity of the first selector while leaving it generic (so it applies to any children).

Undistraction
  • 42,754
  • 56
  • 195
  • 331
  • use `not()` like this `.container:not(#random_ID) > * + *` related https://stackoverflow.com/a/50633333/8620333 / https://stackoverflow.com/a/49742059/8620333 – Temani Afif Mar 28 '19 at 11:06
  • @Temani Afif: An ID is overkill. A simple type selector will suffice: `.container:not(_) > * + *` – BoltClock Mar 28 '19 at 15:33
  • @BoltClock true. I was more about thinking of a selector that will beat any other selector even with mutiple class,pseuo class. – Temani Afif Mar 28 '19 at 16:02
  • @TemaniAfif - If that's the goal, then this is exactly what `!important` is *for*. For all the concern that `!important` is the nuclear option, given the tight restriction of the selector, and the minimal declaration set, it's a pretty surgical strike. – Alohci Mar 28 '19 at 16:21
  • @Alohci indeed. I think we all are moving around and forgetting about `!important` which is the trivial and logical solution here. – Temani Afif Mar 28 '19 at 16:27
  • @Temani Afif: Rereading the question, you're probably right. An ID, or !important, may not be so overkill after all. – BoltClock Mar 28 '19 at 16:38

2 Answers2

2

A more elegant alternative (i.e. one that comes with the additional specificity you need without requiring specificity hacks) is

.container > :not(:first-child)

which is functionally equivalent to your original selector, with a specificity of (0, 2, 0) over the original's (0, 1, 0).

.container {
  margin: 1rem 0;
  border-style: solid;
}

/* 1 class, 1 pseudo-class -> specificity = (0, 2, 0) */
.container > :not(:first-child) {
  margin-top: 1rem;
}

/* 1 class                 -> specificity = (0, 1, 0) */
.child {
  margin: 0;
}
<div class="container">
  <div class="child">Child</div>
  <div class="child">Child</div>
  <div class="child">Child</div>
</div>
<div class="container">
  <div class="child">Child</div>
  <div class="child">Child</div>
  <div class="child">Child</div>
</div>
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • Yet another pseudo-class-based alternative, albeit somewhat less elegant since its meaning is not spelled out in plain English as is the case with the one in my answer, is `.container > :nth-child(n+2)` – BoltClock Mar 28 '19 at 15:48
1

By adding the selector

.container > * + [class] 

You can get a more specific selector it seems for the margin rule.

You can also use the :not() rule as suggested by Temani Afif

.container, .container2, .container3 {
   background-color: goldenrod;
   border: 1px solid black;
   display: block;
   margin-bottom:50px;
}

.container > * + * {
  margin-top: 1rem;
}

.container2 > * + *,
.container2 > * + [class] {
  margin-top: 1rem;
}

.container3:not(#doyoudreamofelectricsheep) > * + * {
  margin-top: 1rem;
}


.child {
  margin: 0;
  height: 20px;
  background-color:red;
  color: black;
  
}

.foobar {
  height: 20px;
  background-color:black;
  color: white;
}
Without the "fix"
<section class="container2">
   <div class="foobar">
       foobar
   </div>
   <div class="child">
       child
   </div>
   <div class="foobar">
       foobar
   </div>
   <div class="child">
       child
   </div>
   <div class="foobar">
       foobar
   </div>
   <div class>
       just class
   </div>
</section>

With the [class] "fix"
<section class="container2">
   <div class="foobar">
       foobar
   </div>
   <div class="child">
       child
   </div>
   <div class="foobar">
       foobar
   </div>
   <div class="child">
       child
   </div>
   <div class="foobar">
       foobar
   </div>
   <div class>
       just class
   </div>
</section>

With the :not(#doyoudreamofelectricsheep) "fix"
<section class="container3">
   <div class="foobar">
       foobar
   </div>
   <div class="child">
       child
   </div>
   <div class="foobar">
       foobar
   </div>
   <div class="child">
       child
   </div>
   <div class="foobar">
       foobar
   </div>
   <div class>
       just class
   </div>
</section>
Tschallacka
  • 27,901
  • 14
  • 88
  • 133
  • 1
    Thanks. This is interesting - you are using a 'class' attribute selector with no value. I wonder what the browser support is like for that. Seems like something a browser might choose to ignore. – Undistraction Mar 28 '19 at 12:36
  • No, they should all just obey it. The class attribute is something that is present or not. So it would ignore `
    ` but it would catch `
    ` it's valid css. This way you can also select for `
    ` with `[data-some-custom-attribute] {color: red;}`. I use it constantly and all mobile browsers and desktop browsers i've tested in, even the old ones support this. I added an example in the snippet.
    – Tschallacka Mar 28 '19 at 12:46
  • 1
    OK. So it acts as a switch. That makes sense. I guess the only disadvantage with your approach is that it requires the child to have at least one class, but that's pretty much a given. – Undistraction Mar 28 '19 at 12:58
  • you can also use the :not() hack. it really is up to you and the rest of your codebase what will help more ensuring you get the correct selector. Also, you notice I implemented the dual selection with the comma separating the two conditions for matching. `.container > * + *, .container * + [class]` which means it selects those without and with class. – Tschallacka Mar 28 '19 at 12:59