Problem with CSS breakpoints selectors override with SASS - css

I have a _display.scss partial.
It contains #mixin and classes related to display CSS property.
_display.scss
#mixin d-block{
display: block;
}
#mixin d-none{
display: none;
}
.d-block{
#include d-block();
}
.d-none{
#include d-none();
}
I developer a #mixin generate-responsive-content that take the #content of a class and generate a different #media query for each breakpoint.
In this way:
.d-block{
#include generate-responsive-content() {
#include d-block();
}
}
.d-none{
#include generate-responsive-content() {
#include d-none();
}
}
// Generate all breakpoints from content
#mixin generate-responsive-content() {
// Responsive styles
// Loop over each size
#each $breakName, $width in $breakpoints {
// Check breakpoint
#if ($breakName != "") {
$breakName: '-' + $breakName;
#media (min-width: $width) {
&#{$breakName} {
#content
}
}
} #else {
#content;
}
}
}
eg. generated classes: .d-block, .d-block-xs, .d-block-sm...
But in this way, I cannot override the classes of .d-none with the classes of .d-block for each breakpoint because they have been generated before and are overwritten by those of .d-none.
I also have a class with the same name but without breakpoint variant, like d-none-lg, d-block-lg, these overwrite all others.
Check this CodePen. Here d-none variants overwrite every class of d-block.
How I can solve that?

I have created a quick demo for you - https://codepen.io/rhythm19/pen/OJVMyLa and its working as expected. I think you just need to swap the order. Generate d-none classes first and then d-block classes.
.d-none{
#include generate-responsive-content() {
#include d-none();
}
}
.d-block{
#include generate-responsive-content() {
#include d-block();
}
}

Updated answer to include max-width breakpoint.
.see{outline:1px solid black;padding:1em;}
// BREAKPOINT
$breakpoints: (
"xs": 575px,
"sm": 576px,
"md": 768px,
"lg": 992px,
"xl": 1200px
);
#mixin d-block() {
display: block;
}
#mixin d-none() {
display: none;
}
.d-block{
#include d-block();
}
.d-none{
#include d-none();
}
// Generate all breakpoints from content
#mixin generate-responsive-content() {
// Responsive styles
// Loop over each size
#each $breakName, $width in $breakpoints {
// Check breakpoint
#if ($breakName == 'xs' ) {
$breakName: '-' + $breakName;
#media (max-width: $width) {
&#{$breakName} {
#content
}
}
}
#else if ($breakName != 'xs' ) {
$breakName: '-' + $breakName;
#media (min-width: $width) {
&#{$breakName} {
#content
}
}
} #else {
#content;
}
}
}
.d-block{
#include generate-responsive-content() {
#include d-block();
}
}
.d-none{
#include generate-responsive-content() {
#include d-none();
}
}
This is what is output:
.see {
outline: 1px solid black;
padding: 1em;
}
.d-block {
display: block;
}
.d-none {
display: none;
}
#media (max-width: 575px) {
.d-block-xs {
display: block;
}
}
#media (min-width: 576px) {
.d-block-sm {
display: block;
}
}
#media (min-width: 768px) {
.d-block-md {
display: block;
}
}
#media (min-width: 992px) {
.d-block-lg {
display: block;
}
}
#media (min-width: 1200px) {
.d-block-xl {
display: block;
}
}
#media (max-width: 575px) {
.d-none-xs {
display: none;
}
}
#media (min-width: 576px) {
.d-none-sm {
display: none;
}
}
#media (min-width: 768px) {
.d-none-md {
display: none;
}
}
#media (min-width: 992px) {
.d-none-lg {
display: none;
}
}
#media (min-width: 1200px) {
.d-none-xl {
display: none;
}
}
UPDATED CODEPEN: Here's the OPs codepen, with this update:
https://codepen.io/chrislafrombois/pen/JjdGKGJ
Here is the code from the pen:
.see {
outline: 1px solid black;
padding: 1em;
}
.d-block {
display: block;
}
.d-none {
display: none;
}
#media (max-width: 575px) {
.d-block-xs {
display: block;
}
}
#media (min-width: 576px) {
.d-block-sm {
display: block;
}
}
#media (min-width: 768px) {
.d-block-md {
display: block;
}
}
#media (min-width: 992px) {
.d-block-lg {
display: block;
}
}
#media (min-width: 1200px) {
.d-block-xl {
display: block;
}
}
#media (max-width: 575px) {
.d-none-xs {
display: none;
}
}
#media (min-width: 576px) {
.d-none-sm {
display: none;
}
}
#media (min-width: 768px) {
.d-none-md {
display: none;
}
}
#media (min-width: 992px) {
.d-none-lg {
display: none;
}
}
#media (min-width: 1200px) {
.d-none-xl {
display: none;
}
}
<div class="see">
<span>I CANNOT SEE ANYTHING BECAUSE d-none OVERWRITE EVERYTHING</span>
<div class="d-none d-none-xs d-block-sm d-block-md d-block-lg">
CHECK CSS STYLE D-BLOCK-LG OVERWRITE EVERITHING
</div>
</div>
Per our discussion, you should not try and put the default d-none and d-block into this mixin. Because of how the code will output, you should just separate that concern and place the defaults before the media query blocks.

Related

SASS #each skip specific line(s) if variable exists

I'm attempting to build a grid system using SASS and so far so good however if the breakpoint is 'xs' I'd like to skip the #media #{$query} { and } line for this as the xs no longer needs a media query.
Can anyone advise?
$default__gridColumns: 18;
$default__flexColumns: 12;
$default__gutter: 20px;
$default__breakpoints: (
'xs': 'screen and (max-width: 767px)',
'sm': 'screen and (min-width:768px) and (max-width:1023px)',
'md': 'screen and (min-width:1024px) and (max-width:1200px)',
'lg': 'screen and (min-width:1201px) and (max-width:1440px)'
);
.flex {
display: flex;
margin: 0 -$default__gutter;
padding: 0 $default__gutter;
> *[class^="span__"] {
padding: 0 $default__gutter;
position: relative;
}
#each $breakpoint, $query in $default__breakpoints {
#media #{$query} {
#for $i from 1 through $default__flexColumns {
> .span__#{$breakpoint}--#{$i} {
width: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--offsetLeft-#{$i} {
margin-left: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--offsetRight-#{$i} {
margin-right: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--push-#{$i} {
left: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--pull-#{$i} {
right: $i/$default__flexColumns * 100%;
}
> .visible__#{$breakpoint} {
display: block !important;
}
> .visible__#{$breakpoint}--flex {
display: flex !important;
}
> .visible__#{$breakpoint}--inline {
display: inline !important;
}
> .visible__#{$breakpoint}--inlineBlock {
display: inline-block !important;
}
> .hidden__#{$breakpoint}--inlineBlock {
display: none !important;
}
}
}
}
}
Updated to use a #mixin.
You can use an #if directive to test the value of the $breakpoint:
$default__gridColumns: 18;
$default__flexColumns: 12;
$default__gutter: 20px;
$default__breakpoints: (
'xs': 'screen and (max-width: 767px)',
'sm': 'screen and (min-width:768px) and (max-width:1023px)',
'md': 'screen and (min-width:1024px) and (max-width:1200px)',
'lg': 'screen and (min-width:1201px) and (max-width:1440px)'
);
#mixin flex_column( $breakpoint ) {
#for $i from 1 through $default__flexColumns {
> .span__#{$breakpoint}--#{$i} {
width: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--offsetLeft-#{$i} {
margin-left: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--offsetRight-#{$i} {
margin-right: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--push-#{$i} {
left: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--pull-#{$i} {
right: $i/$default__flexColumns * 100%;
}
> .visible__#{$breakpoint} {
display: block !important;
}
> .visible__#{$breakpoint}--flex {
display: flex !important;
}
> .visible__#{$breakpoint}--inline {
display: inline !important;
}
> .visible__#{$breakpoint}--inlineBlock {
display: inline-block !important;
}
> .hidden__#{$breakpoint}--inlineBlock {
display: none !important;
}
}
}
.flex {
display: flex;
margin: 0 -$default__gutter;
padding: 0 $default__gutter;
> *[class^="span__"] {
padding: 0 $default__gutter;
position: relative;
}
#each $breakpoint, $query in $default__breakpoints {
#if $breakpoint == xs {
#include flex_column( #{$breakpoint} )
} #else {
#media #{$query} {
#include flex_column( #{$breakpoint} )
}
}
}
}
There's not really a way to skip just the lines you want because of how the CSS will compile with the brackets present in the #media query var.
You can use map-remove
$default__breakpoints: (
'xs': 'screen and (max-width: 767px)',
'sm': 'screen and (min-width:768px) and (max-width:1023px)',
'md': 'screen and (min-width:1024px) and (max-width:1200px)',
'lg': 'screen and (min-width:1201px) and (max-width:1440px)'
);
.flex {
// ...
$custom_breakpoints: map-remove($default__breakpoints, 'xs');
#each $breakpoint, $query in $custom_breakpoints {
#media #{$query} {
// code
}
}
}
It returns a copy of the map so you don't have to worry about it being removed from your $default__breakpoints variable.
EDIT: I misunderstood your question. You could do that with #at-root although that does add one extra indent:
$default__breakpoints: (
'xs': 'screen and (max-width: 767px)',
'sm': 'screen and (min-width:768px) and (max-width:1023px)',
'md': 'screen and (min-width:1024px) and (max-width:1200px)',
'lg': 'screen and (min-width:1201px) and (max-width:1440px)'
);
.flex {
// ...
#each $breakpoint, $query in $default__breakpoints {
#media #{$query} {
#at-root (with: if($breakpoint == 'xs', rule, all)) {
.selector {
content: 'value';
}
}
}
}
}
will compile to
.flex .selector {
content: "value";
}
#media screen and (min-width:768px) and (max-width:1023px) {
.flex .selector {
content: "value";
}
}
#media screen and (min-width:1024px) and (max-width:1200px) {
.flex .selector {
content: "value";
}
}
#media screen and (min-width:1201px) and (max-width:1440px) {
.flex .selector {
content: "value";
}
}

Wrap class list with media queries and suffix in SASS

I'm looking for a way to generate responsive utility classes in SASS. I had this CSS
.text-left { text-align: left; }
.text-right { text-align: right; }
#media (min-width: 480px) {
.text-left-sm { text-align: left; }
.text-right-sm { text-align: right; }
}
#media (min-width: 800px) {
.text-left-md { text-align: left; }
.text-right-md { text-align: right; }
}
and I would like to add some more, but I don't want to repeat myself. It would be best if SASS could generate all those responsive (media query) variants for me. So far I was able to write a mixin that I could call with suffix param and get what I want
#mixin textalign($suffix: "") {
.text-left#{$suffix} { text-align: left; }
.text-right#{$suffix} { text-align: right; }
}
#include textalign();
#media (min-width: 480px) {
#include textalign("-sm");
}
#media (min-width: 600px) {
#include textalign("-lg");
}
but I would like to go one step further and be able to do something like this
/* Unfortunatelly this doesn't work */
#include generate-responsive() {
.text-left { text-align: left; }
.text-right { text-align: right; }
}
Is there a way to achieve something like this? Having a general purpose mixin that I can use to generate all kind of utility classes?
I don't think you can accomplish your goal when nesting your selector in the #include, but you can do it when nesting the #include inside the selector.
SCSS input:
#mixin generate-responsive() {
// Create a list of sizes and widths
$sizes: (
sm: "480px",
md: "600px",
lg: "800px"
);
// Base style, without a suffix
#content;
// Responsive styles
// Loop over each size
#each $suffix, $width in $sizes {
#media (min-width: $width) {
&-#{$suffix} { #content; }
}
}
}
.text-left {
#include generate-responsive() {
text-align: left;
}
}
// You'll have to include the mixin for every class
.text-right {
#include generate-responsive() {
text-align: right;
}
}
CSS output:
.text-left {
text-align: left;
}
#media (min-width: 480px) {
.text-left-sm {
text-align: left;
}
}
#media (min-width: 600px) {
.text-left-md {
text-align: left;
}
}
#media (min-width: 800px) {
.text-left-lg {
text-align: left;
}
}
.text-right {
text-align: right;
// Etc...

SASS: Reverse map-keys

I have a problem with the generated CSS order.
An element should not be visible on phones (but on tablets & desktops), so it gets the following class:
.d-none .d-tablet-block
In the CSS file 'd-none' comes after 'd-tablet-block'. Thus 'd-tablet-block' is always overwritten.
This is my current CSS. It is generated by a SASS Mixin.
#media (min-width: 840px) {
.d-desktop-inline {
display: inline!important
}
}
#media (min-width: 480px) {
.d-tablet-inline {
display: inline!important
}
}
.d-inline {
display: inline!important
}
I need the same CSS but in that order
.d-inline {
display: inline!important
}
#media (min-width: 480px) {
.d-tablet-inline {
display: inline!important
}
}
#media (min-width: 840px) {
.d-desktop-inline {
display: inline!important
}
}
My breakpoints come from Google Web Components. If I put Phone up here and Desktop down, the CSS will be generated in the correct order.
But unfortunately other components of Google Web Components will not work anymore.
$mdc-layout-grid-breakpoints: (
desktop: 840px,
tablet: 480px,
phone: 0
);
Here is the SASS Mixin:
#function breakpoint-infix($name, $breakpoints: $mdc-layout-grid-breakpoints) {
#return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}");
}
#function breakpoint-min($name, $breakpoints: $mdc-layout-grid-breakpoints) {
$min: map-get($breakpoints, $name);
#return if($min != 0, $min, null);
}
#mixin media-breakpoint-up($name, $breakpoints: $mdc-layout-grid-breakpoints) {
$min: breakpoint-min($name, $breakpoints);
#if $min {
#media (min-width: $min) {
#content;
}
} #else {
#content;
}
}
#each $breakpoint in map-keys($mdc-layout-grid-breakpoints) {
#include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $mdc-layout-grid-breakpoints);
.d#{$infix}-none { display: none !important; }
.d#{$infix}-inline { display: inline !important; }
.d#{$infix}-inline-block { display: inline-block !important; }
.d#{$infix}-block { display: block !important; }
.d#{$infix}-table { display: table !important; }
.d#{$infix}-table-row { display: table-row !important; }
.d#{$infix}-table-cell { display: table-cell !important; }
.d#{$infix}-flex { display: flex !important; }
.d#{$infix}-inline-flex { display: inline-flex !important; }
}
}
Can I somehow reverse the map-keys ($ mdc-layout-grid-breakpoints)?

SCSS - best way to organize

Im working with SCSS and I want to structure the code proberly..
In LESS it wasnt a problem, but would you say it is okay to structure the code like below..
imagine that button has its own file.
#mixin button-basic {
.button {
font-size: 14px;
}
}
#mixin button-max-480 {
.button {
color: red;
}
}
#mixin button-max-767 {
.button {
color: green;
}
}
#mixin button-max-959 {
.button {
color: blue;
}
}
#mixin button-min-960 {
.button {
font-size: 34px;
color: purple;
}
}
#media print, screen {
#include button-basic();
}
in my media-query file.. (imagine having multiple includes within each media Query type.)
#media (min-width: 960px) {
#include button-min-960();
}
#media (max-width: 959px) {
#include button-max-959();
}
#media (max-width: 767px) {
#include button-max-767();
}
#media only screen and (max-width:480px) {
#include button-max-480();
}
You could work with #mixins but I would not recommend this approach because this gets really confusing.
I suggest using modifier classes for each variation and use your media-query inside your declaration.
.button {
&--red {
color: red;
}
&--green {
color: green;
}
&--blue {
color: blue;
}
#media (min-width: 768px) {
font-size: 1.125rem;
}
#media (min-width: 960px) {
font-size: 1.25rem;
}
}
This way you have a really clean code base and can split up each component / module into it's own file.

SASS media queries best practise?

Is it ok to nest media queries inside an element? If I want to use min-width: 480px in another places there will be huge repetition. Please look at my code example. Or just use the old way? Any idea?
SASS
.navbar {
height: 500px;
width: 500px;
#media screen and (min-width: 480px) {
background-color: lightgreen;
}
}
.items {
padding: 15px;
color: red;
#media screen and (min-width: 480px) {
border: 1px solid black;
}
}
CSS
#media screen and (min-width: 480px) {
.navbar {
background-color: lightgreen;
}
.items {
border: 1px solid black;
}
}
$pc: 1024px; // PC screen size.
$tablet: 720px; // Tablet screen size.
$phone: 320px; // Phone screen size.
#mixin responsive($media) {
#if $media= phone {
#media only screen and (max-width: $tablet - 1) {
#content;
}
}
#else if $media= tablet {
#media only screen and (min-width: $tablet - 1) and (max-width: $pc) {
#content;
}
}
#else if $media= pc {
#media only screen and (min-width: $pc + 1) and (min-width: $pc) {
#content;
}
}
#else if $media= pc_tablet {
#media only screen and (min-width: $tablet - 1) {
#content;
}
}
}
Examples
body {
#include responsive(pc) {
background: red;
}
#include responsive(tablet) {
background: yellow;
}
#include responsive(phone) {
background: green;
}
}

Resources