Skip to content
Draven·K edited this page Jan 1, 2018 · 8 revisions

The differences between Less and Sass have been studied during the work on my bachelor's thesis proposed by the Czech branch of Red Hat, Inc. The following text is an excerpt from the submitted technical report.

Difference between Less and Sass

In this section, we will explore all the syntactic and semantic differences between Less and Sass that we were able to uncover during our studies of these languages. It will also describe the solutions we proposed to overcome those differences and thus create equivalent Sass representation of Less-specific features.

First, in order to have a~solid ground for sound transformations between Less and Sass files, we need to define the equivalence of dynamic stylesheets written in those preprocessor languages.

Definition 1.1 A Sass file is an equivalent representation of a Less file if and only if the CSS output produced by the two files is equivalent.

In the following sections, we explain the syntactic and semantic differences between Less and Sass. Furthermore, we describe how to convert each of the introduced Less language features into its equivalent Sass representation. Examples of the conversion methods will also be provided.

Variables

The syntactic difference in declaring variables is the leading symbol used for indicating a~variable. In Less, it is the @ symbol, whereas in Sass it is the $ sign. Another difference can be found in the naming rules of variables. Sass treats the dash (-) and underscore (_) in the names of variables and mixins as interchangeable.

The only semantic difference we could find is the ability to store different data types. Less is more dynamic from this perspective, since it allows rulesets to be stored in variables that can be passed to mixins as arguments. However, Sass implements its own way of passing rulesets to mixins.

The variables in both languages are capable of storing selectors. However, Sass throws a syntax error upon trying to store aselector starting with adot (.) representing a class identifier. The solution to this problem is to use the built-in function of Sass called unquote that returns the unquoted form of any string value. It is shown in Example 1.1 and 1.2.

@variable: .bucket;
@{variable} {
  color: blue;
}

Example 1.1. Less: Variable storing class identifier

$variable: unquote(".bucket");
#{$variable} {
  color: blue;
}

Example 1.2. Sass: Variable storing class identifier

Variable defaults

Sass offers its users a~way to assign values to variables in case they had not been set by applying the !default flag just after the assignment.

Variable interpolation

The syntactic difference of this feature can be also seen in Example 1.1 and 1.2. Regarding the semantic difference, Sass does not support dynamic imports and variable names. Hence, the variable interpolation cannot be used in @import statements and variable names. However, there is a~solution for the latter incompatibility. The global scope should create a map of variables consisting of the variables defined inside of it using their names as keys and their references as values. All other scopes should create their own local copy of that global variable rewritten by the merged map of the global and local variable maps as shown in Example 1.4, which is the equal representation of the Less code shown in Example 1.3.

#id {
  @another_var: "This is the content.";
  @var: "another_var";
  content: @@var;
}

Example 1.3. Less: Variable variables

$__l2s__vv: ();

#id {
  $var: "another_var";
  $another_var: "This is the content.";
  $__l2s__vv: map-merge($__l2s__vv, (
    "var": $var,
    "another_var": $another_var
  ));

  content: map-get($__l2s__vv, $var);
}

Example 1.4. Sass: Variable variables

Variable naming

As earlier mentioned, the dash and underscore characters are treated the same way in variable, mixin and function identifiers by Sass. To avoid possible naming collision, the identifiers containing at least one of those characters need to be properly distinguished. In our opinion, the most straigth-forward solution is to double the first occurrence of either the dash or the underscore character. It is important to choose only one of the interchangable characters, otherwise the possibility of naming collision would remain as shown in Example 1.5.

// collision possibility eliminated
@rh-light_green;    => $rh--light_green;
@rh_light_green;    => $rh_light_green;

//collision possibility remains
@rh-light_green;    => $rh--light__green;
@rh_light_green;    => $rh__light__green;

Example 1.5. Naming collision avoidance

Data Types

Both languages support the following main data types:

  • numbers (e.g. 1.2, 13, 10px),
  • strings of text, with and without quotes (e.g. "foo", 'bar', baz),
  • colors (e.g. blue, #04a3f9, rgba(255, 0, 0, 0.5)),
  • list of values, separated by spaces or commas (e.g. 1.5em 1em 0 2em; Helvetica, Arial, sans-serif).

However, on both sides there are either unsupported or nondifferentiated data types present. For instance, Less does not differentiate boolean and null types from unquoted strings and it also does not support map values. On the other hand, the ability to store rulesets in variables is missing from Sass, where the CSS property values, such as Unicode ranges and !important declarations are not differentiated from unquoted strings. Less implements an individual prototype to distinguish the Unicode range from other values.

Numbers

There is a significant difference with respect to how Less and Sass handle units and number operations.

Sass supports unit-based arithmetic. Additionally, Sass implements conversion tables so that any comparable units can be combined. This means, that Sass does not operate on numbers with incompatible units and two numbers with the same unit that are multiplied together produce square units resulting in invalid CSS value as shown in Example 1.6. Sass throws an error on attempt to use such a value.

On the other hand, Less is more lenient regarding unit handling during number operations. It takes the first unit found in the expression and executes the expression as if the remaining numbers were unitless as shown in Example 1.7.

Both Less and Sass implement support for user defined units as a form of future proofing against changes in the W3C specification.

1cm * 1em => 1cm * em       // Error: invalid CSS value
2in * 3in => 6in*in         // Error: invalid CSS value
2in + 3cm + 2pc => 3.5144357in
3in / 2in => 1.5

Example 1.6. Sass: Unit handling

1cm * 1em => 1cm
2in * 3in => 6in
2in + 3cm + 2pc => 3.5144357in
3in / 2in => 1.5

Example 1.7. Less: Unit handling

Precision Another difference in Less and Sass is the number precision they use to calculate with. By default, the number precision used by Less is 8 digits after the decimal point, whereas in Sass it is only 5. However, Sass lets the user define the precision used either programatically or through a command-line argument.

Scoping

The most significant difference between Less and Sass is the way that each of the compared languages handle scoping. According to Matthew Dean, a core member of the team behind developing Less, Sass is an imperative language, whilst Less is declarative \cite{less_vs_sass_scoping}. Both languages are extensions to the CSS in terms of syntax. However, the programming paradigm used in these languages differs. While in a declarative language the programmer only declares the actions to perform, in an imperative language the modus and the order of those actions are also defined. There are two challenges to tackle in terms of finding a conversion method for this difference both originating from the lazy loading feature of Less:

  1. Variables can be used before their declaration. If the given variable or mixin cannot be found in the caller scope, it will look for its value in the parent scopes until it is found or the lookup fails after reaching the end of the global scope. If a variable is defined multiple times in a given scope, the last definition will be used.
  2. A variable is evaluated in place of its use according to its definition using the values of variables reachable from the caller scope. See Example 1.8, where the variable @a is redefined in the caller scope and thus its new value is used during the evaluation.

An equivalent representation in Sass can be achieved by performing the following two actions:

  1. Change the order of variable declarations in each scope, so the declaration always precedes its use.
  2. A variable can reference other variables in its declaration, whose value can be overridden in local scopes. In such cases, the variable needs to be redeclared in those local scopes with its original definition as shown in Example 1.8.
$a: 100%;
$var: $a;

.lazy-eval-scope {
  $a: 9%;
  $var: $a;

  width: $var;
}

Example 1.8. Sass: Lazy loading solution

Extend

There is a syntax-related difference in the way the extend feature is used in the languages being compared. While in Less, it is used like a CSS placeholder, in Sass this feature is represented by a standalone directive. Less presents two ways to use the extend:

  • attached to a selector - it is possible to use multiple extends following each other,
  • inside a ruleset - however, still being attached to the parent selector (& symbol) as seen in Example 1.9.

On the other hand, Sass uses the directive always inside of a ruleset. If there are multiple attached extends to a selector, Sass would place all of them inside of the ruleset.

// animals.less
.animal {
  background-color: black;
  color: white;
}
.bear {
  &:extend(.animal);
  background-color: brown;
}

Example 1.9. Less: Extend inside ruleset

Exact matching

By default, Less looks for exact match between selectors when applying its extend feature. However, it will match selectors regardless of containing single quotes, double quotes, or no quotes as shown in Example 1.10.

// the following extends will not match the specified selectors
:extend(a:hover:visited)        => a:visited:hover
:extend(.class)                 => *.class
:extend(:nth-child(n+3))        => :nth-child(1n+3)

// however, the following extend will match all the specified selectors
:extend([title='identifier'])   => [title='identifier'], 
                                   [title="identifier"],
                                   [title=identifier]

Example 1.10. Less: Extend only exact matching selectors

Sass, on the other hand, understands the logic behind CSS selectors and is able to tell, whether two selectors are equal, even if the match is not exact. To create an equivalent Sass representation for such situations - also seen in Example 1.11 - it is necessary to remove the affected extends from the Sass code as shown in Example 1.12.

.a.class {
  color: blue;
}
.test:extend(.class) {} // this will NOT match the selector above

Example 1.11. Less: Exact match not found

.a.class {
  color: blue;
}
.test {} // extend should be removed, not an exact match

Example 1.12. Sass: Exact match not found

Multiple extended selectors

In Less, each member of a selector list can have one or multiple extend pseudo-classes attached to it. Since Sass does not support such a language construct, it is needed to refactor such a list of selectors to achieve equal CSS output. It can be achieved by segregating the list of selectors into separate declaration rules of those selectors that have at least one extend pseudo-class attached to it. All extends should be put inside the ruleset. To the remaining list of selectors - even if it is empty - a placeholder selector should be appended. Afterwards, the created placeholder selector should extend all the segregated selectors, as demonstrated in Example 1.13 and 1.14.

.big-division:extend(.division),
.big-bag,
.big-bucket:extend(.bag):extend(.bucket) {
  // body
}

Example 1.13. Less: Multiple extended selectors

.big-division {
    @extend .division;
    @extend %_.big-bag;
}
.big-bucket {
    @extend .bag;
    @extend .bucket;
    @extend %_.big-bag;
}
.big-bag, %_.big-bag {
  // body
}

Example 1.14. Sass: Multiple extended selectors

Placeholder selector Note that placeholder selector names must start with the % character which cannot be followed by the # nor . characters. In addition to these, it cannot contain a * nor whitespace characters. All other characters also accepted by selectors can be used when generating placeholder names.

Extending nested selectors

Less is able to match and extend nested selectors, as opposed to Sass. In order to create the equal representation of this feature, a new placeholder selector should be created and used to extend the nested selector. The ruleset of the nested selector should be copied into the placeholder selector as seen in Example 1.15.

The placeholder name should be generated out of the selector list having the aforementioned rules in mind. The prohibited characters should be escaped by replacing them with a chosen sequence of characters. This cannot lead to naming collisions, since placeholders are not specified by Less.

* #a .class {
  color: blue;
}
%__uni__s#a__s.class {
  // placeholder name generated from the selector list
  color: blue;
}
a {
  @extend %__uni__s__#a__s.class;
}

Example 1.15. Sass: Extending nested selectors

Extend inside @media rulesets

Based on the language feature specification of both languages, this feature should work the same way. However, we found a case, where Sass fails its defined behaviour. According to the Sass language specification, the use of @extend within @media (or other CSS directives) may only extend selectors that appear within the same directive block. However, if the referenced selector by the @extend is also present in the outer scope, it fails applying the @extend rule and throws an error message as demonstrated by Example 1.16 and 1.17.

The raised error can be solved by creating a placeholder selector just like in the example above and reference it instead of the original selector. The newly created placeholder will not be present in the outer scope, thereby not leading to the same problem.

@media print {
  .selector { // this should be matched - it is in the same directive
    color: black;
  }
  .screenClass{
    @extend .selector; // extend inside media
  }
}
.selector { // ruleset on top of style sheet - extend should ignore it
  color: red;
}

Example 1.16. Sass: Extend inside directives

You may not @extend an outer selector from within @media.
You may only @extend selectors within the same directive.
From "@extend .selector" on line 6.

Example 1.17. Output: Extend inside directives

The error has been reported to the core contributors of Sass and can be found in the issue tracker of the repository.

Extend "all"

In Less, extend can take an optional parameter --- the keyword "all". When it is applied, the extend modifies its behaviour and starts to act exactly like the @extend diretive of Sass by matching the selector as part of another selector as shown in Example 1.18.

// extend_all.less
.a.b.test,
.test.c {
  color: orange;
}

.replacement:extend(.test all) {}

Example 1.18. Less: Extend all

Duplication detection

Sass implements the selector duplication detection feature. When merging selectors, @extend is smart enough to avoid unnecessary duplication, i.e. .seriousError.seriousError gets translated to .seriousError. In addition, it will not produce selectors that cannot match anything, like #main#footer. On the other hand, Less has no duplication detection implemented. Despite the possibly different CSS being generated because of the lack of duplication detection, the equivalence of the stylesheet generators is unaffected.

Mixins

There is a significant difference in the syntax of mixins as language constructs. In Less, all selectors are implicitly perceived as mixin definitions. On the contrary, Sass defines its own syntax for defining mixins.

Another syntax-related difference can be found in the set of allowed characters regarding the mixin names. In Less, any selector can be used as the name for a mixin. However, Sass does not allow non-alphanumeric characters, except the _ (underscore), in its mixin identifiers. In addition, the identifier cannot start with a digit.

Scoping

The most significant semantic difference is in the scoping. In Less, the mixin definitions with the same identifier do not override each other. Actually, Less applies all the matching mixins of the caller scope. If none are defined, it will search the outer (parent) scopes until it finds at least one matching mixin and applies all the matching mixins of that scope.

However, Sass does not implement such a~behaviour. We have identified two approaches in terms of finding a conversion method for this difference:

  • Fusion This approach merges all the matched mixin definitions into a single definition that will be applied upon calling the mixin.
  • Enumeration Enumeration converts each of the matched definitions into an individual mixin definition and instead of calling only one mixin, all the created mixins will be called in the order Less would execute them. However, the naming of the mixins should be reasonably resolved, i.e. by appending a serial number to them.

Both the aforementioned approaches will be used in order to not only provide an equal but also a readable and user-friendly Sass representation of the mixin features.

Access to variables Another difference to be solved is the handling of locally undefined variables. If a variable is undefined in the local scope of the mixin, both stylesheet generators will start to look for the value in the parent scope moving towards the global execution context. If the value cannot be found, Sass throws an error announcing that the variable is not defined. However, Less does not stop there. It will also take a look at the caller scope.

To solve this difference, there are two cases that need to be further investigated:

  1. The variable is defined in one of the parent scopes of the mixin declaration.
  2. The variable is defined in the caller scope or one of its parent scopes.

In the first case, no action has to be taken, since both Less and Sass apply the same default behaviour and will take the value of the variable found in one of the parent scopes as shown in Example 1.19 and 1.20.

@x: 1;
.mixin() {
  z-index: @x; // x = 1
}
#namespace {
  .mixin();
}

Example 1.19. Less: Parent scope variable

$x: 1;
@mixin __class__mixin() {
  z-index: $x;
}
#namespace {
  @include __class__mixin();
}

Example 1.20. Sass: Parent scope variable

In the second case, the parameter list of the mixin should be extended by the locally undefined variable. When calling this mixin, the missing variable should be passed along with the other parameters as demonstrated in Example 1.21 and 1.22. This method will be used in conjunction either with fusion or enumeration whenever the conditions of this case are met.

.a(@a) {
  z-index: @x;
}
#namespace {
  @x: 1;
  .a("val");
}

Example 1.21. Less: Caller scope variable

@mixin __class__a($a, $x) {
  z-index: $x;
}
#namespace {
  $x: 1;
  @include __class__a("val", $x: $x);
}

Example 1.22. Sass: Caller scope variable

Selectors as mixins

In case of selectors as mixins, fusion will be applied. The name of the created mixin should be properly sanitized based on the regulations set by the parser of Sass. The conversion of this feature is shown in Example 1.23 and 1.24.

.a {
  // body
}
.a() {
  // different body
}
.class {
  .a()
}

Example 1.23. Less: Selector as mixin

@mixin __class__a {
  // body of .a selector
  // body of .a() mixin
}
.a {
  // body
}
.class {
  @include __class__a;
}

Example 1.24. Sass: Selector as mixin

Namespaces

Selectors in Less can behave like mixins and they can also be nested. Hence, Less offers a~way to organize the defined mixins using the namespacing feature. Sass cannot organize its mixin definitions in such means. The solution is to copy the mixin definition and place it outside the namespace. The identifier of the copied mixin should reflect the namespacing structure as shown in Example 1.25 and 1.26.

\end{minipage}

#outer {
  .inner {
    color: red;
  }
}
.c {
  #outer > .inner;
}

Example 1.25. Less: Namespacing

@mixin __id__outer__class__inner {
  // the body of the namespaced mixin
}
#outer {
  .inner {
    //body
  }
}
.c {
  @include __id__outer__class__inner;
}

Example 1.26. Sass: Namespacing

Namespace and mixin guards

Less implements guards similar to CSS and its @media and @supports conditions instead of control directives as it is in case of Sass. The solution is to convert the guards to an equal representation of @if statements. The created control directive should be placed inside of the mixin definition encasing its content in both the namespace and mixin guard cases as demonstrated in Example \ref{lst_compare:less_guards} and \ref{lst_compare:sass_guards}. In case of namespace guards, all of the mixin definitions placed inside of the namespace should inherit the created control directive.

#namespace when (@mode=huge) {
  .mixin() {
    // body
  }
}

Example 1.27. Less: Namespace and mixin gurads

@mixin __id__namespace__class__mixin() {
  @if $mode == huge {
    // body
  }
}

Example 1.28. Sass: Namespace and mixin gurads

Important keyword

Less implements a shorthand for marking all property values defined in a particular mixin as !important. There is no language construct in Sass that would make this feature possible to reproduce. However, it can be solved by creating an extra parameter called $__l2s__important with a default value set to null as shown in Example 1.29 and 1.30. In order to keep the converted Sass code as clean as possible, this extra parameter should be added only to mixins that are called with the important keyword.

.foo() {
  color: #fff;
}
.important {
  .foo() !important;
}

Example 1.29. Less: The !important keyword

@mixin class_foo ($__l2s__important: null) {
  color: #fff #{$__l2s__important};
}
.important {
  @include class_foo($__l2s__important: !important);
}

Example 1.30. Sass: The !important keyword

Parametric mixins

To create an equal representation of parametric mixins, enumeration will be used. Fusion cannot be applied in this case because the arguments can be set with default values. In addition, each mixin can specify a different value for the same argument. To resolve the naming of the parametric mixins, each of the converted mixin should be provided with a serial number appended to its name as seen in Example 1.32, which is the equal Sass representation of Example 1.31.

.mixin(@color; @value: uppercase) {
  // body
}
.mixin(@color; @value: 2; @margin: 2) {
  // body
}
.selector {
  .mixin(#008000);
}

Example 1.31. Less: Parametric mixins

@mixin __class__mixin__1($color, $value: uppercase) {
  // body
}
@mixin __class__mixin__2($color; $value: 2; $margin: 2) {
  // body
}
.selector {
  @include __class__mixin__1(#008000);
  @include __class__mixin__2(#008000);
}

Example 1.32. Sass: Parametric mixins

@arguments variable The @argument variable has a special meaning inside Less mixins. It contains all the arguments passed to it, when it was called. Sass does not implement such a special variable, thus it needs to be created by the converter. It should create the variable $arguments that will have a simple list containing all the parameters specified in the mixin definition as shown in Example 1.33.

@mixin class_box-shadow($x: 0, $y: 0, $blur: 1px, $color: #000) {
  $arguments: ($x, $y, $blur, $color);
  // body
}

Example 1.33. Sass: @arguments variable

Pattern matching In Less, different types of mixins exist and all possess a particular behaviour. One of those is pattern matching. This feature can be seen as an abstraction of a hypothetical switch directive demonstrated in Example 1.34. The converter should identify the mixins implementing this feature and apply the fusion approach to merge them into one mixin definition. This feature can be then converted into the @if-@else if Sass control directive as shown in Example 1.35. The body of the mixin definition that accepts a wildcard parameter should be placed outside of the created control directive, since it would be always called.

.mixin(dark; @color) {
  color: darken(@color, 10%);
}
.mixin(light; @color) {
  color: lighten(@color, 10%);
}
.mixin(@_; @color) {
  display: block;
}

@switch: light;
.class {
  .mixin(@switch; #888);
}

Example 1.34. Less: Pattern matching

@mixin __class__mixin($switch, $color) {
  @if $switch == dark {
    color: darken($color, 10%);
  } @else if $switch == light {
    color: lighten($color, 10%);
  }

  display: block;
}

$switch: light;
.class {
  @include __class__mixin($switch, #888);
}

Example 1.35. Sass: Pattern matching

Nested mixins

Nested mixins are syntactic constructs, that are not allowed in Sass. Those constructs should be detached and placed into the context of their parent mixin. The detached mixins can reference variables that are defined in their former parent mixin. In such case, those variables should be imported into the definition body of the detached mixin, as well. In addition, they should also inherit all the parameters of their parent mixin, so they could be called with the same parameters as shown in Example 1.36 and 1.37. That way can we ensure that the detached mixins will have access to the same context as if they were nested. However, the drawback of this method is the redundant code it creates, and thus breaking the DRY principle.

.unlock(@value) { // outer mixin
  @value: 2 * @value;
  .doSomething() { // nested mixin
    declaration: @value;
  }
}
#namespace {
  .unlock(5); // unlock doSomething mixin
  .doSomething(); // mixin defined in doSomething became callable
}

Example 1.36. Less: Nested mixins

@mixin __class__unlock($value) {
  $value: 2 * $value;
  //body
}
@mixin __class__doSomething($value) {
  $value: 2 * $value;
  declaration: $value;
}
#namespace {
  @include __class__unlock(5);
  @include __class__doSomething(5);
}

Example 1.37. Sass: Nested mixins

Mixins as functions

All mixins in Less possess a default behaviour. The variables and mixins defined inside of them are reachable from the caller scope. Due to this feature, it is necessary to modify the way the converter handles the scoping difference.

If a variable or a mixin is not defined in the caller scope, the converter should look first inside of the definition of all mixins being called in the caller scope before moving to the outer scopes. In case the variable definition is found in one of those mixins, a function directive should be created out of that definition and named after the missing variable. After that, the referenced variable should be replaced by a function call. The created function, as well as the mixins, can reference to variables defined in the caller scope. In such case the same principle should be applied as in Example 1.22. The parameter list of the function should be extended by the undefined variables, i.e. the variable @x in Example 1.38 and 1.39.

.mixin() {
  @width:  100% / @x;
  // body
}
.caller {
  @x: 3;
  .mixin();
  width:  @width;
}

Example 1.38. Less: Mixins as functions

@mixin __class__mixin($x) {
  $width: 100% / $x;
  // body
}
@function width($x) {
  @return 100% / $x;
}
.caller {
  $x: 3;
  @include __class__mixin($x);
  width: width($x);
}

Example 1.39. Sass: Mixins as functions

Passing rulesets to mixins

Both in Less and Sass it is possible to pass rulesets to mixins. Less uses detached rulesets, whereas Sass uses content blocks. The main difference is, that Less is able to pass multiple rulesets to a mixin via parameters, since detached rulesets can be stored in variables as demonstrated in Example 1.40. On the other hand, Sass does not support rulesets as data type. It can pass only one content block to the mixin, which is accessed via a special directive inside of that mixin - the @content directive as shown in Example 1.41.

Another difference is, that while in Less it is possible to define mixins inside of a detached ruleset, Sass cannot do the same with its content block. In Less, this feature leads to a~special behaviour of the detached rulesets. Both definition and caller scopes are available to them. If both scopes contain the same variable or mixin, the declaration scope value takes precedence. In addition, by including a detached ruleset into any other structure (e.g. another ruleset or mixin definition) all its properties (including the defined mixins and variables) will be accessible to the scope the detached ruleset had been included into. Using this feature, it is possible to dynamically call different implementations of the same mixin by calling the detached ruleset in different scopes. Sass cannot call mixins dynamically. Due to these characteristics of detached rulesets, we have not found an effective way - which would not break the DRY principles - to convert this feature into an equal Sass representation. After consultation with Red Hat, we decided to find an effective conversion method during the future work on the converter, since they do not use this feature of Less in their projects.

.apply-to-ie6-only(@content) {
    @content();
}
.apply-to-ie6-only({
  #logo {
    color: #fff;
  }
});

Example 1.40. Less: Detached ruleset

@mixin apply-to-ie6-only {
    @content;
}
@include apply-to-ie6-only {
  #logo {
    color: #fff;
  }
}

Example 1.41. Sass: Passing content block

Import Directive

Both stylesheet generators implement the @import directive for importing other files that are either CSS or another dynamic stylesheet. However, there are certain restrictions in Sass regarding this feature.

The imported file will be compiled as CSS, if:

  • its extension is .css,
  • the filename begins with http://,
  • the filename is a url(),
  • the @import has any media queries.

If none of the above conditions are met and the extension is .scss or .sass, then the named Sass or SCSS file will be imported. If there is no extension, Sass will try to find a file with that name and one of the sass extensions and import it. If the extension is any other, than .css, .scss or .sass, Sass will not be able to read it, even if it contains valid Sass code. However, Less treats those files as if they were valid Less files and will try to compile them. To solve the difference, the converter will have to append a .scss or .sass extension to those filenames.

Import options

Less implements the following options that modify the behaviour of the @import directive:

  • reference Compiles a Less file but does not include it into the final output.
  • inline Includes the source file in the output without processing it.
  • less Treats the import as a Less file regardless its extension.
  • css Treats the import as a CSS file regardless its extension.
  • once Includes the file only once (default behaviour).
  • multiple Includes the file even if it had been previously imported.
  • optional Continues in compilation if the file is not found.

More than one keyword per @import is allowed in Less. However, Sass natively supports only the options once and reference, where the former is the default behaviour, too. The latter option can be achieved by using Sass partials. All the other options cannot be reproduced in a Sass. However, the converter may manipulate the imported file extensions in order to achieve same results. The converter may even omit the @import directives containing the optional keyword if the file being imported cannot be found.

There is no known way to reproduce the funcionality of the inline option in Sass.

CSS Guards

Guards can be also applied to CSS selectors in Less. The Sass representation of these guards is an enclosing @if directive. However, if the guard is appended to a base-level rule containing the parent-selector-referencing character (&), Sass should omit that selector and enclose only its child rules as shown in Example 1.42 and 1.43.

@my-option: true;

& when (@my-option = true) {
  button {
    color: white;
  }
  a {
    color: blue;
  }
}

Example 1.42. Less: CSS guard

$my-option: true;

@if $my-option == true {
  button {
    color: white;
  }
  a {
    color: blue;
  }
}

Example 1.43. Sass: CSS guard

Loops

Loops in Less are achieved through recursively called guarded mixins as shown in Example 1.44. This behavior can be also reproduced in Sass using the @if control directive as demonstrated in Example 1.45.

.generate-columns(4);

.generate-columns(@n, @i: 1) when (@i =< @n) {
  .column-@{i} {
    width: (@i * 100% / @n);
  }
  .generate-columns(@n, (@i + 1));
}

Example 1.44. Less: Loops

@mixin generate-columns($n, $i: 1) {
  @if $i <= $n {
    .column-#{$i} {
      width: ($i * 100% / $n);
    }
    @include generate-columns($n, ($i + 1));
  }
}

@include generate-columns(4);

Example 1.45. Sass: Loops

Merge

This feature is unique to Less. We have not found a solution, that could express such a feature using the existing language constructs of Sass, despite of the fact that Sass possesses a wide range of built-in functions for list manipulation.

Parent Selector

Parent selector reference is available in both languages. They work absolutely the same way with one exception. Sass is unable to use multiple parent selector references joined by an empty string (&&) - shown in Example 1.46. The solution is to create a "placeholder variable", which stores the value of the parent selector and use its interpolated form instead. It can be seen in Example 1.47.

.link {
  & + & {
    color: red;
  }
  & & {
    color: green;
  }
  && {
    color: blue;
  }
  &, &ish {
    color: cyan;
  }
}

Example 1.46. Less: Parent selector

.link {
  $__l2s__parent: &;
  & + & {
    color: red;
  }
  & & {
    color: green;
  }
  &#{$__l2s__parent} {
    color: blue;
  }
  &, &ish {
    color: cyan;
  }
}

Example 1.47. Sass: Parent selector

JavaScript Evaluation

Sass is not able to evaluate JavaScript, at all. Hence, the JavaScript code has to be evaluated by the converter and the returned value must be output in the generated Sass code, instead. The equivalent Sass representation of Example 1.48 can be seen in Example 1.49.

@var: `"hello world".toUpperCase() + '!'`;

Example 1.48. Less: JavaScript Evaluation

$var: "HELLO WORLD!";

Example 1.49. Sass: JavaScript Evaluation

Clone this wiki locally