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...
Related
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.
I want to use multiple include in the same SCSS. For example:
.section-ptb {
padding-top: 130px;
padding-bottom: 130px;
#include desktop {
padding-top: 80px;
padding-bottom: 80px;
}
#include tablet {
padding-top: 80px;
padding-bottom: 80px;
}
#include mobole {
padding-top: 80px;
padding-bottom: 80px;
}
}
it's very Boring to Multiple #include frequently. Have Any Way to Reduce The Code, I want to use:
.section-ptb {
padding-top: 130px;
padding-bottom: 130px;
#include desktop , #include tablet, #include mobole {
padding-top: 80px;
padding-bottom: 80px;
}
}
But It's Not Valid SCSS. Please Tell Me Another Way To Reduce The Code.
As mentioned by #karthick dynamic includes are not supported (yet). In your case I think it would make sense to have a single mixin to handle all media queries – like:
SCSS
// map holding breakpoint values
$breakpoints: (
mobile : 0px,
tablet : 680px,
desktop: 960px
);
// mixin to print out media queries (based on map keys passed)
#mixin media($keys...){
#each $key in $keys {
#media (min-width: map-get($breakpoints, $key)){
#content
}
}
}
.section-ptb {
padding-top: 130px;
padding-bottom: 130px;
// pass the key(s) of the media queries you want to print
#include media(mobile, tablet, desktop){
padding-top: 80px;
padding-bottom: 80px;
}
}
CSS Output
.section-ptb {
padding-top: 130px;
padding-bottom: 130px;
}
#media (min-width: 0px) {
.section-ptb {
padding-top: 80px;
padding-bottom: 80px;
}
}
#media (min-width: 680px) {
.section-ptb {
padding-top: 80px;
padding-bottom: 80px;
}
}
#media (min-width: 960px) {
.section-ptb {
padding-top: 80px;
padding-bottom: 80px;
}
}
You can use something like this:
SCSS
#mixin phone {
#media /*conditions*/ {
#content
}
}
#mixin tablet {
#media /*conditions*/ {
#content
}
}
#mixin desktop {
#media /*conditions*/ {
#content
}
}
#mixin media($keys...) {
#each $key in $keys {
#if ($key == phone) {
#include phone {
#content
}
} #else if ($key == tablet) {
#include tablet {
#content
}
} #else if ($key == desktop) {
#include desktop {
#content
}
}
}
}
USAGE
#include media(phone, tablet, desktop) {
// your scss code
}
#include media(tablet, desktop) {
// your scss code
}
#include media(phone) {
// your scss code
}
// and so on...
How can I define multiple names for the same mixin, with support for content blocks?
Definition
#mixin desktop-breakpoint {
#media only screen and (min-width: 769px) {
#content;
}
}
#mixin large-breakpoint {
#include desktop-breakpoint;
}
Usage
.my-class {
font-size: small;
#include desktop-breakpoint {
font-size: big;
}
}
.my-other-class {
color: red;
#include large-breakpoint {
color: blue;
}
}
Error message
Mixin "large-breakpoint" does not accept a content block.
You're not passing any #content when using #include desktop-breakpoint in your large-breakpoint mixin. Doing this will fix your compilation error:
#mixin large-breakpoint {
// Remember to pass content received by mixin to #include
#include desktop-breakpoint {
#content;
}
}
Then your CSS will be compiled properly, as intended:
.my-class {
font-size: small;
}
#media only screen and (min-width: 769px) {
.my-class {
font-size: big;
}
}
.my-other-class {
color: red;
}
#media only screen and (min-width: 769px) {
.my-other-class {
color: blue;
}
}
See proof-of-concept example based on your modified code: https://www.sassmeister.com/gist/3109af060293eed0b89a22c27fa20527
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.
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;
}
}