So I ran to some very unique situation when working on grav-ovacoda-theme.
First let us take a look at some HTML, you just need to pay attention to the ul.nav-items
.
<ul class="nav-items mobile">
<li class="nav-item">
<a href="#">
<i class="fas fa-bars"></i>
</a>
</li>
</ul>
<ul class="nav-items desktop">
<li class="nav-item">
<a href="#">
Home
</a>
</li>
<li class="nav-item">
<a href="#">
Blog
</a>
</li>
</ul>
ul.nav-items.mobile {
@extend .d-flex, .d-sm-none;
}
ul.nav-items.desktop {
@extend .d-none, .d-sm-flex;
}
You can probably guess I was trying to do some responsive design here, hiding some div and showing another on different screen size.
And I thought this is going to work. That is not the case.
The 2nd ul
works fine. However, with the first ul
, the d-sm-none
didn't override the d-flex
. Strange! So I tried to change the class order in @extend
, and added .mobile
and .desktop
to see if I can fix the issue, but nothing works.
I was quite lost at this point, but I do know one thing! This is probably a CSS priority issue. So I decided to check out
-
The 100 measure
One quick way to figure out how ??powerful?? a CSS rule is, is by measuring the specificty of the selectors:
- #id selectors are worth 100
- .class selectors are worth 10
- tag selectors are worth 1
-
CSS Specificity | MDN Web Docs.
<div id="test"> <span>Text</span> </div>
div#test span { color: green; } div span { color: blue; } span { color: red; }
No matter the order, text will be green because that rule is most specific. (Also, the rule for blue overwrites the rule for red, notwithstanding the order of the rules)
So I was like, yeah my .d-sm-none
is probably the one with the issue here.
See if you can spot the issue in this scss @each
loop I wrote
$display: (
"none": none,
"flex": flex
);
$breakpoints: (
"sm": 576px,
);
@each $display, $type in $display {
.d-#{$type} {
display: $type;
}
@each $breakpoint, $value in $breakpoints {
@media only screen and (min-width: $value) {
.d-#{$breakpoint}-#{$type} {
display: $type;
}
}
}
}
Which compiles to:
.d-none {
display: none;
}
@media only screen and (min-width: 576px) {
.d-sm-none {
display: none;
}
}
.d-flex {
display: flex;
}
@media only screen and (min-width: 576px) {
.d-sm-flex {
display: flex;
}
}
I have always thought @media
will override a regular class but NO! they are not.
This is where the 2nd realization came in, @extend didnt add the line of css into the style that I am writing, it is adding the classes to the d-sm-none
!
CSS compiled with @extend
:
.d-none, ul.nav-items.desktop {
display: none;
}
@media only screen and (min-width: 576px) {
.d-sm-none, ul.nav-items.mobile {
display: none;
}
}
/* this display flex overriding d-sm-none */
.d-flex, ul.nav-items.mobile {
display: flex;
}
@media only screen and (min-width: 576px) {
.d-sm-flex, ul.nav-items.desktop {
display: flex;
}
}
The compiled css is not being overrided by any type selectors, class selectors, or id selectors, etc (thanks to @extend
only adding class to css instead of being an actually css line). Guess what, the order of the class
is now the priority which is whatever comes after.
You can clearly see why d-sm-none
is not working now, the .d-flex
rule right under it is overiding the display property.
And the fix is quite simple now, to have all the styling working correctly, I just need to create the class without @media
first, and then adding in the rest.
@each $display, $type in $display {
.d-#{$type} {
display: $type;
}
}
@each $display, $type in $display {
@each $breakpoint, $value in main.$v-breakpoints {
@media only screen and (min-width: $value) {
.d-#{$breakpoint}-#{$type} {
display: $type;
}
}
}
}
/* Regular class moves up */
.d-none {
display: none;
}
.d-flex {
display: flex;
}
/* Media query comes after */
@media only screen and (min-width: 576px) {
.d-sm-none {
display: none;
}
}
@media only screen and (min-width: 576px) {
.d-sm-flex {
display: flex;
}
}
Resource: Specifishity some visualization for the css priorities that I was talking about