I am building a variable-driven LESS framework in which a child theme can override variables set in the parent theme. This framework includes responsive design, so variables have different values set for every media query. In order for the media query specific variables to take effect, the same styles that create the main layout have to also exist within the media queries. To make this as dynamic as possible, I have created layout less files that I want to import at every media query. Unfortunately, LESS only imports styles once and ignores all subsequent imports of the same file.
Here's the gist what it looks like:
style.less:
#import "variables"; // has the variables for the main layout
#import "variables-calculations"; // has all the dynamic element size calculations that change with the defined variables
#import "layouts"; // has all of the styles that incorporate the variables
#import "responsive-tablet-wide";
media-query-tablet-wide.less
#media screen and (max-width: 1199px) {
#import "variables-responsive-tablet-wide"; // has all the variables for the current breakpoint
#import "variables-calculations";
#import "layouts";
}
The resulting output for the media query? Empty:
#media screen and (max-width: 1199px) {
}
I am using LESS compiler Prepros.
How can I force LESS to compile "layouts" twice? Or 5 times, for that matter.
I should define your variables in one file and append the target / device to it to discriminate between them
so variables should define:
#var1: 15;
#var1-tablet: 30;
etc.
Main reason, see http://lesscss.org/:
When defining a variable twice, the last definition of the variable is
used, searching from the current scope upwards. This is similar to css
itself where the last property inside a definition is used to
determine the value.
Related
EDIT: This question was marked as a duplicate of this one, but see the addendum near the end of this answer to see what that question doesn't ask, and what the answer doesn't answer.
I'm working on a web app that uses Bootstrap 3. I have a basic 3-layer override architecture, where 1) Bootstrap's _variables.scss contains the core variables, 2) _app-variables.scss contains the base app variables that override Bootstrap's _variables.scss, and 3) _client-variables.scss contains client-specific customizations that override _app-variables.scss. Either #2 or #3 (or both) can be blank files. So here's the override order:
_variables.scss // Bootstrap's core
_app-variables.scss // App base
_client-variables.scss // Client-specific
Simple enough in theory, but a problem arises because of what I'll call "variable dependencies" -- where variables are defined as other variables. For example:
$brand: blue;
$text: $brand;
Now, let's say the above variables are defined in _variables.scss. Then let's say in _app-variables.scss, I override only the $brand variable to make it red: $brand: red. Since SASS interprets the code line by line sequentially, it will first set $brand to blue, then it will set $text to blue (because $brand is blue at that point), and finally it will set $brand to red. So the end result is that changing $brand afterwards does not affect any variables that were based on the old value of $brand:
_variables.scss
---------------------
$brand: blue;
$text: $brand; // $text = blue
.
.
.
_app-variables.scss
---------------------
$brand: red; // this does not affect $text, b/c $text was already set to blue above.
But obviously that's not what I want - I want my change of $brand to affect everything that depends on it. In order to properly override variables, I'm currently just making a full copy of _variables.scss into _app-variables.scss, and then making modifications within _app-variables from that point. And similarly I'm making a full copy of _app-variables.scss into _client-variables.scss and then making modifications within _client-variables.scss at that point. Obviously this is less than ideal (understatement) from a maintenance point of view - everytime I make a modification to _variables.scss (in the case of a Bootstrap upgrade) or _app-variables.scss, I have to manual trickle the changes down the file override stack. And plus I'm having to redeclare a ton of variables that I may not even be overriding.
I found out that LESS has what they call "lazy loading" (http://lesscss.org/features/#variables-feature-lazy-loading), where the last definition of a variable is used everywhere, even before the last definition. I believe this would solve my problem. But does anyone know a proper variable-override solution using SASS?
ADDENDUM:
Here's one technique I've already thought through: include the files in reverse order, using !default for all variables (this technique was also suggested in the answer to this question). So here's how this would play out:
_app-variables.scss
---------------------
$brand: red !default; // $brand is set to red here, overriding _variables.scss's blue.
.
.
.
_variables.scss
---------------------
$brand: blue !default; // brand already set in _app-variables.scss, so not overridden here.
$text: $brand !default; // $text = red (desired behavior)
So that solution is almost perfect. However, now in my override files, I don't have access to variables defined in Bootstrap's _variables.scss, which I would need if I wanted to define my variable overrides (or my own additional custom variables) using other Bootstrap variables. For example, I might want to do: $custom-var: $grid-gutter-width / 2;
Solved, but I don’t know from which version this works. I believe the solution could have always been available. Tested on:
> sassc --version
sassc: 3.2.1
libsass: 3.2.5
sass2scss: 1.0.3
We are going to use a simplified environment, so filenames do not match with Bootstrap’s.
Challenge
Given a framework we do not control (for example installed only on the Continuous Integration environment and not available in our machines) that expresses SCSS variables in the following manner:
// bootstrap/_variables.scss
$brand-primary: #f00 !default;
$brand-warning: #f50 !default;
$link-color: $brand-primary !default;
And given a file in that same framework that uses the variables:
// bootstrap/main.scss
a:link, a:visited {
color: $link-color;
}
The challenge is:
Include the framework in your own application’s SCSS in such a way that
variables’ dependencies in the framework are preserved and honors;
you can depend in on the default values but still be able to change the results on the framework dependencies.
More precisely:
Include the framework in your application’s SCSS in such a way that $brand-color will always be the inverse of $brand-warning, whatever its value is in the framework.
Solution
The main file would look like this:
// application.scss
#import "variables";
#import "bootstrap/variables";
#import "bootstrap/main";
And your variables file would look like this:
// _variables.scss
%scope {
#import "bootstrap/variables";
$brand-primary: invert($brand-warning) !global;
}
Results:
> sassc main.scss
a {
color: blue; }
Explanation
The %scope part is not something magic of SCSS, it’s simply a hidden class with the name scope, available exclusively for later extensions with #extend. We are using it just to create a variable scope (hence the name).
Inside the scope we #import the framework’s variables. Because at this moment there’s no value for each variable every variable is created and assigned its !default value.
But here’s the gimmick. The variables are not global, but local. We can access them but they are not going to pollute the global scope, the one that will be later used to derive variables inside the framework.
In fact, when we want to define our variables, we want them global, and indeed we use the !global keyword to signal SCSS to store them in the global scope.
Caveats
There’s one major caveat: you cannot use your own variables while you define them.
That means that in this file
%scope {
#import "bootstrap/variables";
$brand-primary: black !global;
#debug $brand-primary;
}
The #debug statement will print the default value defined in bootstrap/_variables.scss, not black.
Solution
Split variables in two parts:
%scope {
#import "bootstrap/variables";
$brand-primary: black !global;
#debug $brand-primary;
}
#debug $brand-primary;
The second #debug will indeed correctly print black.
With Bootstrap 4 or bootstrap-sass all variables set in the _variables.scss with the !default flag.
Therefore, if you set a variable before bootstrap's _variables.scss is included, when it is included, the value from _variables.scss will be ignored.
So my sass entry file might look like this ...
#import "bootstrap-overrides";
#import "bootstrap/scss/bootstrap-flex";
#import "mixins/module";
In alpha 6 of Bootstrap 4, all variables in _variables.scss can be overridden in _custom.scss, in the way that mryarbles describes.
However, the overrides do not cascade to other elements, because the inclusion order is:
#import "variables";
#import "mixins";
#import "custom";
When I change this to
#import "custom";
#import "variables";
#import "mixins";
it works as expected.
The _custom.scss file in BS4 dev branch has been removed. Try to reorder your imports with this order:
Setup
#import "client-variables";
#import "app-variables";
#import "boostrap";
#import "app-mixins";
#import "client-mixins";
Make sure to copy the content of boostrap variable file _variables.scss to app-variables and client-variables. Leave the !default beside each variable to allow further override.
Explanation
All bootstrap variables are declared with !default. From Sass reference:
You can assign to variables if they aren’t already assigned by adding the !default flag to the end of the value. This means that if the variable has already been assigned to, it won’t be re-assigned, but if it doesn’t have a value yet, it will be given one.
Bootstrap will respect all variables already defined on top making the app_variables with higher priority and client_variables with highest priority.
You need to copy all the variable declarations from bootstrap _variables into app-variables and client-variables so you can have a custom variable as you wanted. (Disadvantage is that it is harder to maintain on every bootstrap update)
All variables are now available in your app-mixins and client-mixins
I would like to use .offcanvas-sm which is assigned to an <nav> element into the Less file. The Less file looks like:
#import "../../jasny-bootstrap.less";
.test {
.offcanvas-sm;
}
Problem is that the Less processor says - class offcanvas-sm doesn't exist. Its from this "https://github.com/jasny/bootstrap/blob/master/less/offcanvas.less" Less file included in "jasny-bootstrap.less". But how can I import this code to a class?
The compiler is correct there, indeed .offcanvas-sm does not exist in the context you try to invoke it. The key word here is Scope: selectors defined in a media query can be used as a mixin only within this same media query block.
For this particular case extend will do the trick. Scope handling of the extend is somewhat orthogonal to that of mixins, so selectors defined within media query blocks are open for "extending" from an outer scope (but not in opposite):
.test {
&:extend(.offcanvas-sm all);
}
Or just:
.test:extend(.offcanvas-sm all) {
}
---
all keyword is necessary in this case since .offcanvas-sm style is actually a set of two rulesets: .offcanvas-sm and .offcanvas-sm.in
I tried to use breakpoint to replace a media query in _responsive.scss (see line 155) of a subtheme of the Zen 7.5.4 Drupal base theme:
// #media all and (min-width: 960px)
#include breakpoint($desktop)
{
$zen-column-count: 5;
…
Before that I installed breakpoint, required in config.rb, included and defined my breakpoints in _init.scss.
// Breakpoints
$breakpoint-no-query-fallbacks: true;
$small: 480px, 'no-query' '.lt-ie9';
$desktop: 960px, 'no-query' '.lt-ie9';
A simpler task works flawlessly (so the system works) however the mentioned code creates the following error:
error styles.scss (Line 118 of _breakpoint.scss: Base-level rules cannot contain the parent-selector-referencing character '&'.)
I tried to find the '&' in the code of zen-grids, but I did not find it. What do I wrong?
As Thamas said, Breakpoint's no-query fallback is meant to be used from within a selector; the fallbacks get prepended to the selector string with a space, so they cannot be used outside of a selector.
This is what's going on:
Sass with Breakpoint:
$small: 480px, 'no-query' '.lt-ie9';
.foo {
content: 'bar';
#include breakpoint($small) {
content: 'baz';
}
}
Plain Sass:
.foo {
content: 'bar';
#media (min-width: 480px) {
content: 'baz';
}
.lt-ie9 & {
content: 'baz';
}
}
It is important to note that Breakpoint does not create a separated global context, so the code you've provided that sets $zen-column-count inside of your Breakpoint include will not restrict that to that breakpoint.
The recommended workflow for working with media queries, and the workflow Breakpoint was built for, was not one where all media queries of one type are grouped together, but rather one where media queries are used in-line to adjust individual elements as they are needed. This goes hand-in-hand with the recommendation that you do not use device based media queriers, but rather media queries that are content based; i.e. media queries chosen because the current component no longer looks good and needs to be adjusted.
"What do I wrong?"
I did not read. The error message says that the problem is in _breakpoint.scss which belongs to Breakpoint and not to Zen.
And it is not a bug, it is "by desing". Breakpoint is a mixin which is designed to be included in a selector, so it is meaningless to #include at the root level of an .scss file.
It worth to mention that Sass enables root level #include but it is restricted to use without any properties or parent references (breakpoint have these, that was the problem) – see: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#including_a_mixin
I've used compass to create new sass project with zurb foundation 4 framework. My screen.scss file looks following:
// Reset and normalization settings
#import "normalize";
// Global Foundation Settings
#import "settings";
// Comment out this import if you are customizing you imports below
#import "foundation";
Default settings contains following line:
$row-width: 62.5em;
It means that our grid row has to be 1000px (62.5em) for screens that are at least 768px.
What is the proper way to add one more media condition, which will increase $row-width variable up to 75em (1200px) for screens that are at least 1280px?
Create breakpoints for .row in app.scss, like this:
.row{
#media #{$medium} {
max-width: 75em;
}
}
Foundation has variables for screen sizes that are used for media queries in _visibility.scss, for example $medium translates to "only screen and (min-width:80em)".
I haven't tried it myself yet, but should work.
Also, look at this answer, i just modified it a bit:
https://stackoverflow.com/a/14247136/961064
You can find other variables for different screen sizes in _visibility.scss: https://github.com/zurb/foundation/blob/master/scss/foundation/components/_visibility.scss
In a Meteor (nodejs) project we use the less CSS preprocessor, and we use 3rd party "bootstrap-full.less" for our css styling.
There is one (maybe more) CSS rule in bootstrap that I would like to nuke, because it conditionally overrides other rules. (details below)
However, I don't want to "hack" the original bootstrap file, cause that is "vendor code".
I know I could re-override the CSS rules, but this is more work and hassle.
So the question is:
Is it possible to manipulate/process the parsed css rules in less before the actual css is generated?
In particular, there is this rule here,
#media (max-width: 767px) {
...
// Make all grid-sized elements block level again
[class*="span"],
.row-fluid [class*="span"] {
float: none;
display: block;
width: auto;
margin-left: 0;
}
which is undesirable in my case, because we only have this on a sidebar, that keeps the same width even on mobile. So it should continue to behave like a table with cells (span1, span2 etc) being floated.
Ok, maybe I will figure out a different solution for my CSS / bootstrap problem, but still it would be interesting to know if less allows me to manipulate the css it produces.
What I've done in my project is create a master .less file and within that file import my third party less files and then following that my custom files. Any classes that you want to update, create a dupe .less file with that class in it in your own directory and then simply edit the properties you want to change in your files. So for example:
master.less
#import "/static/bootstrap/less/bootstrap.less";
// My custom files
#import "scaffolding.less";
#import "type.less";
And then you have your own file called
type.less
h6{
color: #myCustomColor;
}
This way you keep all the bootstrap files intact and only overwrite what you need to. It also keeps the files nicely seperated so it's easy to navigate and also a snap if you ever need to update the bootstrap source.