Can I update SASS variables in Media Queries? - css

Consider the following SASS code. I want to make sure that if the screen is above 1250px then the margin-top should be 750px and then it should change depending on size. However SASS does not allow me to update the variables inside the strings.
// Above 1250px
$pageTemplateMargin:750px;
// Below 1250px
#media screen and (max-width:1250px){
$pageTemplateMargin:550px;
}
// Below 950px
#media screen and (max-width:950px){
$pageTemplateMargin:450px;
}
#media screen and (max-width:850px){
$pageTemplateMargin:150px;
}
#media screen and (max-width:750px){
$pageTemplateMargin:250px;
}
// Render the correct code
.page-template {margin-top:$pageTemplateMargin}
Is there a better way for this, as it does not work and page-template stays at 750px.
Thanks

I agree with the accepted answer that it's better to use maps in this case but I want to point out a couple of things.
Variables can actually be updated inside of media queries. The problem is that a variable defined outside of a block is a global variable while one defined within a block is a local variable. You can let sass treat a variable within a block as a global variable using the !global keyword.
$pageTemplateMargin:750px;
#media screen and (max-width:1250px){
$pageTemplateMargin: 550px !global;
}
.page-template {
margin-top: $pageTemplateMargin //will use 550px instead of 750px
}
Just want to clarify that it is possible albeit it is not appropriate in this use case.
I also suggest using a loop for your code which will prove helpful especially if you add more screen widths and margin properties so you don't need to further write more media queries.
$breakpoints: (
1200px: 10px,
1000px: 15px,
800px: 20px,
);
#each $width, $margin in $breakpoints {
#media screen and (max-width: $width) {
.element {
margin-top: $margin;
}
}
}
Hope this helps.

No, you can't (in this situation, as pointed out in the other answer).
I'd suggest using mixins to work with this:
#mixin pageTemplateMargin($px) {
margin-top: $px
}
#media screen and (max-width:1250px) {
.element { #include pageTemplateMargin(10px);}
}
#media screen and (max-width:1000px) {
.element { #include pageTemplateMargin(15px);}
}
#media screen and (max-width:800px) {
.element { #include pageTemplateMargin(20px);}
}
There's also a way of mapping through sass objects, such as:
$breakpoints: (
1200: 10px,
1000: 15px,
800: 20px,
);
#media screen and (max-width:1200px) {
.element { margin-top: map-get($breakpoints, 1200);}
}
#media screen and (max-width:1000px) {
.element { margin-top: map-get($breakpoints, 1000);}
}
#media screen and (max-width:800px) {
.element { margin-top: map-get($breakpoints, 800);}
}
This would allow you to globally change the margin by adjusting 1 variable.
Working codepen example

I have tried this then i fixed my issue. It will calculate all media-breakpoint automatically by given rate (base-size/rate-size)
$base-size: 16;
$rate-size-xl: 24;
// set default size for all cases;
:root {
--size: #{$base-size};
}
// if it's smaller then LG it will set size rate to 16/16;
// example: if size set to 14px, it will be 14px * 16 / 16 = 14px
#include media-breakpoint-down(lg) {
:root {
--size: #{$base-size};
}
}
// if it is bigger then XL it will set size rate to 24/16;
// example: if size set to 14px, it will be 14px * 24 / 16 = 21px
#include media-breakpoint-up(xl) {
:root {
--size: #{$rate-size-xl};
}
}
#function size($px) {
#return calc(#{$px} / $base-size * var(--size));
}
div {
font-size: size(14px);
width: size(150px);
}

Related

Modify CSS variable in media query

Is there a way to modify a previously-declared CSS variable inside a media query with just vanilla CSS? What I'm after would look like this (which of course doesn't work as the variables all get computed in the end):
#container {
--elem-size: 20px;
}
#media only screen and (min-width: 800px) {
#container {
--elem-size: calc(var(--elem-size) * 2);
}
}
I'm aware that it would be possible to declare a "base variable" (e.g. --base-elem-size) and then use it to generate new variable values for different viewports. However, I'm working with a very large number of CSS variables which makes it undesirable to create a duplicate base set out of them. The ideal solution would be able to "modify" a previously-declared value.
This might be far from ideal but to some degrees does define the style in one line. It uses fallback values of CSS variables.
Open full screen and change window size to see the result:
:root {
--elem-size-base: 50px;
}
#container {
/* πŸ‘‡ Fallback for base value, optional 2nd fallback for no variable value */
width: var(--elem-size, var(--elem-size-base, 50px));
height: var(--elem-size, var(--elem-size-base, 50px));
outline: 2px solid #000;
background-color: pink;
}
#media only screen and (min-width: 768px) {
#container {
/* πŸ‘‡ This variable for various #media */
--elem-size: calc(var(--elem-size-base) * 2);
background-color: lightblue;
}
}
#media only screen and (min-width: 1024px) {
#container {
/* πŸ‘‡ This variable for various #media */
--elem-size: calc(var(--elem-size-base) * 4);
background-color: lightgreen;
}
}
<div id="container"></div>
I think having base CSS variables would make your CSS much more readable and easier to maintain, specially if you are working with a big codebase.
May be something like this:
:root {
/* Define you base variables */
--font-size-default: 16px;
--font-size--large: calc(var(--font-size-default) * 1.25);
/* Assign the base variable to the element variables */
--elem-size: var(--font-size-default);
}
#media screen and (min-width: 840px) {
:root {
/* Change base variable used for element variable */
--elem-size: var(--font-size--large);
}
}
This allows you to set a base font-size variable and one (or more) "variants" of that variable, which you can then use on your whole project
I would suggest you look into something like the Open Props (which has some really great ideas for what base variables can be and how to use them)

Using the same css layout but for differently sized media

I use CSS grid and Sass, and I use different grid layouts for different screen sizes (phone, tablet, desktop). But for some pages, I would like the same layouts but chosen at slightly bigger or smaller screens than for other pages.
Is something like that possible? Or am I approaching it from the wrong angle? My current solution (see below) works, but duplicates the styles a lot.
In more detail:
I have 3 different grids that are chosen according to the screen size.
.hero {
&__container {
grid-template-areas:
"header"
"image"
"text";
}
#media min-width: 1000px {
grid-template-areas:
"header header header"
"text ... image"
"text ... image";
}
// other definitions for this screen size
}
#media min-width: 1300px {
grid-template-areas:
"header header image"
"text ... image"
"text ... image";
}
// other definitions for this screen size
}
}
&__header {
grid-area: header;
font-size: 2.5rem;
#media min-width: 1000px {
font-size: 2.8rem;
}
#media min-width: 1300px {
font-size: 3.2rem;
}
}
...
}
They are used in about 20 similar web pages.
<div class="page_a">
<div class="hero">
<div class="hero__container">
<div class="hero__header">...</div>
<div class="hero__text">...</div>
<div class="hero__image">...</div>
</div>
</div>
</div>
The layout is very similar, but I would like to switch to different layouts at a different point based on the specifics of the content: the header text length, the size & importance of the image, etc.
What I would like to do is something like this:
.page_a {
.hero {
// redefine nothing, use defaults
}
}
.page_c {
.hero {
// the header is longer so we need a bigger screen to switch to the biggest layout
// somehow say that the 1300px layout should be used from 1500px
}
}
The only thing I managed to do is to simply redefine all the grids at each possible point (the default points + the custom points), which means the code is very repetitive:
.page_c {
.hero {
// use 1000px layout also for 1300px - the whole thing has to be repeated
#media min-width: 1300px {
grid-template-areas:
"header header header"
"text ... image"
"text ... image";
}
// other definitions for this size
}
// use 1300px layout for 1500px - the whole thing has to be repeated
#media min-width: 1500px {
grid-template-areas:
"header header image"
"text ... image"
"text ... image";
}
// other definitions for this size
}
}
}
Which means that every time I change some layout I have to go to all the places it is used at various size and change it too.
Here you go...
Your problem could be solved with SASS or SCSS, more precisely with a #mixin. I'm using SCSS because I'm more familiar with it, but you could also use SASS.
What is a #mixin?
As stated on SASS official website: Mixins allow you to define styles that can be re-used throughout your stylesheet. First you define a #mixin then you call it later in your code with an #include. Every #mixin should have a unique name. For example: define a #mixin layout_600 and call it with an #include layout_600.
Two things are important when defining a #mixin:
A #mixin should be defined before you call it with an #include. Otherwise, SCSS will try to call something that isn't defined yet (it is defined but later in your stylesheet).
A #mixin should be defined outside of your nested code (ideally at the top of your stylesheet). If you define a #mixin inside your nested code, you won't be able to call it later when you want to change default styles. The easiest way for you to understand what I mean is to show you the correct way and the wrong way.
Correct:
#mixin layout_600 {
font-size: 3rem;
color: blue;
font-weight: 700;
}
.hero {
&__header {
#media (min-width: 600px) {
#include layout_600;
}
}
}
.page_b {
.hero {
// Use the 600px layout also for the 1000px.
&__header {
#media (min-width: 1000px) {
// It will work.
#include layout_600;
}
}
}
}
Wrong:
.hero {
&__header {
#media (min-width: 600px) {
#mixin layout_600 {
font-size: 3rem;
color: blue;
font-weight: 700;
}
}
}
}
.page_b {
.hero {
// Use the 600px layout also for the 1000px.
&__header {
#media (min-width: 1000px) {
// It won't work.
#include layout_600;
}
}
}
}
You need to write a #mixin for each layout that you want to have (e.g. 600px, 1000px). You only need to do it once for every layout but you can call a particular #mixin n-times. This is perfect because:
you don't have to re-write your code, you just call a particular #mixin with an #include as many times as you want and
if you want to change your styling, you do it just once in your #mixin and the style will be changed in every place that is referring to this #mixin.
Working example
Before changing the default style
I defined three #mixins like this:
when: window width < 600px,
when: 600px < window width < 1000px and
when: window width > 1000px.
As you can see, the font-size and the color is different at different window width. The font is getting bigger and the color goes from black to blue to red as the window is getting wider. By the way, in the right upper corner I added a div that shows the current window width.
Here's a live demo before changing the default style.
After changing the default style
I decided that for the page_b I would use the 600px layout (i.e. #mixin layout_600) also for the 1000px. This can easily be done by calling the #mixin layout_600 with the #include layout_600 like this:
.page_b {
.hero {
// Use the 600px layout also for the 1000px.
&__header {
#media (min-width: 1000px) {
#include layout_600;
}
}
}
}
As you can see, the style of the page_b when window width is actually 1000px is the same as if the window width is 600px (smaller font and blue color).
Here's a live demo after changing the default style.
Customizing a #mixin
Also, it's possible to customize a #mixin if you want. For example, I used the 600px layout (i.e. #mixin layout_600) but changed the color from red to green. This can be done like this:
.page_b {
.hero {
// Use the 600px layout also for the 1000px.
&__header {
#media (min-width: 1000px) {
#include layout_600;
color: green; // Customize the mixin.
}
}
}
}
As you can see, the color should be blue (as in #mixin layout_600) but it's green.
Here's a live demo after customizing a #mixin.

How to implement max-font-size?

I want to specify my font size using vw, as in
font-size: 3vw;
However, I also want to limit the font size to say 36px. How can I achieve the equivalent of max-font-size, which does not exist--is the only option to use media queries?
font-size: 3vw; means that the font size will be 3% of the viewport width. So when the viewport width is 1200px - the font size will be 3% * 1200px = 36px.
So a max-font-size of 36px can be easily implemented using a single media query to override the default 3vw font-size value.
Codepen demo (Resize Browser)
div {
font-size: 3vw;
}
#media screen and (min-width: 1200px) {
div {
font-size: 36px;
}
}
<div>hello</div>
Update: With the new CSS min() function, we can simplify the above code - without using media queries (caniuse)
div {
font-size: min(3vw, 36px);
}
In the above example, the font-size will be at most 36px, but will decrease to 3vw if the the viewport is less than 1200px wide (where 3vw computes to a value less than 36px )
That being said, using viewport units for font-size in the above way is problematic because when the viewport width is much smaller - say 320px - then the rendered font size will become 0.03 x 320 = 9.6px which is very (too) small.
In order to overcome this problem, I can recommend using a technique called Fluid Type AKA CSS Locks.
A CSS lock is a specific kind of CSS value calculation where:
there is a minimum value and a maximum value,
and two breakpoints (usually based on the viewport width),
and between those breakpoints, the actual value goes linearly from the minimum to the maximum.
So let's say we want to apply the above technique such that the minimum font-size is 16px at a viewport width of 600px or less, and will increase linearly until it reaches a maximum of 32px at a viewport width of 1200px.
This can be represented as follows (see this CSS-tricks article for more details):
div {
font-size: 16px;
}
#media screen and (min-width: 600px) {
div {
font-size: calc(16px + 16 * ((100vw - 600px) / 600));
}
}
#media screen and (min-width: 1200px) {
div {
font-size: 32px;
}
}
Alternatively, we could use this SASS mixin which does all of the math for us so that the CSS would look something like this:
/*
1) Set a min-font-size of 16px when viewport width < 600px
2) Set a max-font-size of 32px when viewport width > 1200px and
3) linearly increase the font-size from 16->32px
between a viewport width of 600px-> 1200px
*/
div {
#include fluid-type(font-size, 600px, 1200px, 16px, 32px);
}
// ----
// libsass (v3.3.6)
// ----
// =========================================================================
//
// PRECISE CONTROL OVER RESPONSIVE TYPOGRAPHY FOR SASS
// ---------------------------------------------------
// Indrek Paas #indrekpaas
//
// Inspired by Mike Riethmuller's Precise control over responsive typography
//
//
// `strip-unit()` function by Hugo Giraudel
//
// 11.08.2016 Remove redundant `&` self-reference
// 31.03.2016 Remove redundant parenthesis from output
// 02.10.2015 Add support for multiple properties
// 24.04.2015 Initial release
//
// =========================================================================
#function strip-unit($value) {
#return $value / ($value * 0 + 1);
}
#mixin fluid-type($properties, $min-vw, $max-vw, $min-value, $max-value) {
#each $property in $properties {
#{$property}: $min-value;
}
#media screen and (min-width: $min-vw) {
#each $property in $properties {
#{$property}: calc(#{$min-value} + #{strip-unit($max-value - $min-value)} * (100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)});
}
}
#media screen and (min-width: $max-vw) {
#each $property in $properties {
#{$property}: $max-value;
}
}
}
// Usage:
// ======
// /* Single property */
// html {
// #include fluid-type(font-size, 320px, 1366px, 14px, 18px);
// }
// /* Multiple properties with same values */
// h1 {
// #include fluid-type(padding-bottom padding-top, 20em, 70em, 2em, 4em);
// }
////////////////////////////////////////////////////////////////////////////
div {
#include fluid-type(font-size, 600px, 1200px, 16px, 32px);
}
#media screen and (max-width: 600px) {
div {
font-size: 16px;
}
}
#media screen and (min-width: 1200px) {
div {
font-size: 36px;
}
}
<div>Responsive Typography technique known as Fluid Type or CSS Locks.
Resize the browser window to see the effect.
</div>
Codepen Demo
Update: We can use the new clamp() CSS function (caniuse) to refactor the above code to simply:
div {
font-size: clamp(16px, 3vw, 32px);
}
see MDN:
clamp() allows you to set a font-size that grows with the size of the
viewport, but doesn't go below a minimum font-size or above a maximum
font-size. It has the same effect as the code in Fluid Typography but
in one line, and without the use of media queries.
p { font-size: clamp(1rem, 2.5vw, 1.5rem); }
<p>
If 2.5vw is less than 1rem, the font-size will be 1rem.
If 2.5vw is greater than 1.5rem, the font-size will be 1.5rem.
Otherwise, it will be 2.5vw.
</p>
--
Further Reading
Fluid Typography
How Do You Do max-font-size in CSS?
Fluid Responsive Typography With CSS Poly Fluid Sizing
Non-linear interpolation in CSS
Here is another idea. The calc function uses double precision float. Therefore it exhibits a step function near 1e18. For example,
width: calc(6e18px + 100vw - 6e18px);
This will snap to values 0px, 1024px, 2048px, etc. see pen https://codepen.io/jdhenckel/pen/bQNgyW
The step function can be used to create abs value and min/max with some clever maths. For instance
max(x, y) = x - (x + y) * step(y - x)
Given step(z) is zero when z<0 and one otherwise.
just an idea, not very practical, but maybe fun to try.
(Caution: this technique depends on an implementation detail that is not in any specification; currently, it works in Chrome and Safari, but not in Firefox, Edge or Internet Explorer, which don’t use double-precision floats for CSS values.)
UPDATE: this post is no longer useful (was it ever?) since CSS now supports min, max, and clamp.
Another way increases font size slowly, this will not limit max font size, but even on very wide screens, it will look better. Does not answer question in perfect way, but its 1 line...
font-size: calc(16px + 1vw);
Update: CSS improved and i recommend using clamp(min, preferred, max) function:
font-size: clamp(12px, 2vw, 20px);
At some point, the font-size exceeds the 36px scale right, find that. Assuming, it exceeds when the width: 2048px:
#media screen and (min-width: 2048px) {
.selector {
font-size: 36px;
}
}
Yes, unfortunately you need to use the #media queries. I hope that doesn't affect anything.
According to this website (there are ads in this site),
If you don't want to use clamp():
font-size: 24px;
font-size: min(max(3.5vw, 16px), 40px);
Line one for IE.
Line two for others, means font-size:3.5vw, max-font-size:40px, min-font-size:16px.

Responsive Typography with rem - Baseline fontsize in % or px?

I am trying to implement a responsive typography solution with SCSS using rems in order to use media queries only on the baseline font-size in html and not on every element.
I found different approaches to the font size in html:
html { font-size: 100%; } // uses the default browser font-size
html { font-size: 62.5%; } // 62.5% = 10px to facilitate rem calculation
html { font-size: 16px; } // uses 16px
Assuming that I would take the 62.5%-approach suggested by Jonathan Snook, I could then assign my headings and paragraph quiet easily using a mixin for px-fallbacks:
#mixin font-size($sizeValue: 1.6) {
font-size: ($sizeValue * 10) + px;
font-size: $sizeValue + rem;
}
h1 { #include font-size(3.2); } // 32px
h2 { #include font-size(2.6); } // 26px
h3 { #include font-size(2.2); } // 22px
h4 { #include font-size(1.8); } // 18px
h5 { #include font-size(1.6); } // 16px
h6 { #include font-size(1.4); } // 14px
I could then apply media queries to the html font-sizes to scale the typography at different resolutions , something like this:
#media (min-width: 768px) { html { font-size: 56.3%; } } // 56.3% = 9px
#media (min-width: 992px) { html { font-size: 62.5%; } } // 62.5% = 10px
#media (min-width: 1200px) { html { font-size: 68.8%; } } // 68.8% = 11px
My questions:
1. What is the best approach to the font-size baseline (px vs 100% vs 62.5%)?
2. What is the best overall approach to responsive typography with SASS / SCSS?
I don't think it's the "best approach", but this is what I've been doing lately:
Set your base size in pixels. It will be consistent across browsers and is easier to reason about.
Baseline in pixels, all type sizes for elements in rems, and then just reset your baseline size on media queries (in pixels).
This is a great article by Trent Walton that is less about implementation and more about best practices, but I find it really helpful: http://trentwalton.com/2012/06/19/fluid-type/
Not sure I answered your question, but it's a pretty in-depth topic!
My SCSS fluid typography, very comfortably:
html {
#include fluid-property(font-size, 1rem, 1.5rem, 320px, 1600px);
}
/// Fluid property/properties
/// Examples:
/// html { #include fluid-property(font-size, 14px, 20px, 320px, 1366px); } // single property
/// html { #include fluid-property(font-size, 1em, 2em, 320px, 1366px); } // single property
/// h1,h2,h3,h4,h5 { #include fluid-property((padding-left, padding-right), 10rem, 20rem, 48em, 64em); } // multiple properties with same values
/// All units must be of the type: px / em / rem.
#mixin fluid-property($properties, $min-value, $max-value, $min-screen, $max-screen) {
#each $property in $properties {
#{$property}: $min-value;
}
#include media-min($min-screen) {
#each $property in $properties {
#{$property}: calc-interpolation($min-value, $max-value, $min-screen, $max-screen);
}
}
#include media-min($max-screen) {
#each $property in $properties {
#{$property}: $max-value;
}
}
}
#function calc-interpolation($min-value, $max-value, $min-screen, $max-screen) {
$u-min-value: unit($min-value);
$a: 0; $b: 0;
#if ($u-min-value == em) {
$a: em($max-value - $min-value) / em($max-screen - $min-screen);
$b: $min-value - $a * em($min-screen);
} #else if ($u-min-value == rem) {
$a: rem($max-value - $min-value) / rem($max-screen - $min-screen);
$b: $min-value - $a * rem($min-screen);
} #else {
$a: px($max-value - $min-value) / px($max-screen - $min-screen);
$b: $min-value - $a * px($min-screen);
}
$sign: "+";
#if ($b < 0) {
$sign: "-";
$b: abs($b);
}
#return calc(#{$a*100}vw #{$sign} #{$b});
}
It will render next CSS:
html { font-size: 1rem; }
#media only screen and (min-width: 320px) { html { font-size: calc(0.625vw + 0.875rem); } }
#media only screen and (min-width: 1600px) { html { font-size: 1.5rem; } }

Passing css property:value as sass mixin argument

Is something like the following dummyexample possible in sass/scss?
I need something like this to prevent the endless mediaquery repeats for different devices.
// The dummy mixin
#mixin iphone_rules($webapp_portrait:null){
#if($webapp_portrait != null){
// Portrait (webapp)
#media screen and (max-height: 460px){
// The following line is just a dummy
eg. echo $webapp_portrait;
}
}
}
// How I want to use it
.mySelector{
margin-left:10px;
padding:0px;
#include iphone_rules('margin-left:20px; padding:2px;');
}
Sass does not allow the use of arbitrary strings in place of the property/value syntax.
For most mixins, the #content directive is your best bet for passing in styling information:
#mixin iphone_rules {
#media screen and (max-height: 460px){
#content;
}
}
.mySelector {
#include iphone_rules {
margin-left:10px;
padding:0px;
}
}
Otherwise, styling information can be passed in as a mapping (or list of lists for Sass 3.2 and older):
#mixin iphone_rules($styles: ()) {
#media screen and (max-height: 460px){
#each $p, $v in $styles {
#{$p}: $v;
}
}
}
.mySelector {
margin-left:10px;
padding:0px;
#include iphone_rules(('margin-left': 20px, 'padding': 2px));
}

Resources