SCSS – A CSS Preprocessor Cont.


Today I again started with the SCSS(Sassy CSS), picking  up from where I have left yesterday. Today I learnt the following :-

Variables

are the essence of any programming language. They allow us to reuse values without having to copy them over and over again. Most importantly, they make updating a value very easy. No more find and replace or manual crawling.

However CSS is nothing but a huge basket containing all our eggs. Unlike many languages, there are no real scopes in CSS. Because of this, we have to pay real attention when adding variables at the risk of witnessing conflicts.

My advice would be to only create variables when it makes sense to do so. Do not initiate new variables for the heck of it, it won’t help. A new variable should be created only when all of the following criteria are met:

  • the value is repeated at least twice;
  • the value is likely to be updated at least once;
  • all occurrences of the value are tied to the variable (i.e. not by coincidence).

Basically, there is no point declaring a variable that will never be updated or that is only being used at a single place.

Scoping

Variable scoping in Sass has changed over the years. Until fairly recently, variable declarations within rulesets and other scopes were local by default. However when there was already a global variable with the same name, the local assignment would change the global variable. Since version 3.4, Sass now properly tackles the concept of scopes and create a new local variable instead.

The docs talk about global variable shadowing. When declaring a variable that already exists on the global scope in an inner scope (selector, function, mixin…), the local variable is said to be shadowing the global one. Basically, it overrides it just for the local scope.

The following code snippet explains the variable shadowing concept.

// Initialize a global variable at root level.
$variable: 'initial value';

// Create a mixin that overrides that global variable.
@mixin global-variable-overriding {
  $variable: 'mixin value' !global;
}

.local-scope::before {
  // Create a local variable that shadows the global one.
  $variable: 'local value';

  // Include the mixin: it overrides the global variable.
  @include global-variable-overriding;

  // Print the variable’s value.
  // It is the **local** one, since it shadows the global one.
  content: $variable;
}

// Print the variable in another selector that does no shadowing.
// It is the **global** one, as expected.
.other-local-scope::before {
  content: $variable;
}

!default Flag

When building a library, a framework, a grid system or any piece of Sass that is intended to be distributed and used by external developers, all configuration variables should be defined with the !default flag so they can be overwritten.

$baseline: 1em !default;

Thanks to this, a developer can define their own $baseline variable before importing your library without seeing their value redefined.

// Developer’s own variable
$baseline: 2em;

// Your library declaring `$baseline`
@import 'your-library';

// $baseline == 2em;

!global Flag

The !global flag should only be used when overriding a global variable from a local scope. When defining a variable at root level, the !global flag should be omitted.

// Yep
$baseline: 2em;

// Nope
$baseline: 2em !global;

Multiple Variables Or Maps

There are advantages of using maps rather than multiple distinct variables. The main one is the ability to loop over a map, which is not possible with distinct variables.

Another pro of using a map is the ability to create a little getter function to provide a friendlier API. For instance, consider the following Sass code:

/// Z-indexes map, gathering all Z layers of the application
/// @access private
/// @type Map
/// @prop {String} key - Layer’s name
/// @prop {Number} value - Z value mapped to the key
$z-indexes: (
  'modal': 5000,
  'dropdown': 4000,
  'default': 1,
  'below': -1,
);

/// Get a z-index value from a layer name
/// @access public
/// @param {String} $layer - Layer’s name
/// @return {Number}
/// @require $z-indexes
@function z($layer) {
  @return map-get($z-indexes, $layer);
}

Extend

The @extend directive is a powerful feature that is frequently misunderstood. In general, it makes it possible to tell Sass to style a selector A as though it also matched selector B. Needless to say, this can be a valuable ally when writing modular CSS.

However, the true purpose of @extend is to maintain the relationships (constraints) within extended selectors between rulesets. What exactly does this mean?

  • Selectors have constraints (e.g. .bar in .foo > .bar must have a parent .foo);
  • These constraints are carried over to the extending selector (e.g. .baz { @extend .bar; } will produce .foo > .bar, .foo > .baz);
  • The declarations of the extended selector will be shared with the extending selector.

Given that, it’s straightforward to see how extending selectors with lenient constraints can lead to selector explosion. If .baz .qux extends .foo .bar, the resulting selector can be .foo .baz .qux or .baz .foo .qux, as both .fooand .baz are general ancestors. They can be parents, grandparents, etc.

Always try to define relationships via selector placeholders, not actual selectors. This will give you the freedom to use (and change) any naming convention you have for your selectors, and since relationships are only defined once inside the placeholders, you are far less likely to produce unintended selectors.

For inheriting styles, only use @extend if the extending .class or %placeholderselector is a kind of the extended selector. For instance, an .error is a kind of .warning, so .error can @extend .warning.

%button {
  display: inline-block;
  // … button styles

  // Relationship: a %button that is a child of a %modal
  %modal > & {
    display: block;
  }
}

.button {
  @extend %button;
}

// Yep
.modal {
  @extend %modal;
}

// Nope
.modal {
  @extend %modal;

  > .button {
    @extend %button;
  }
}

There are many scenarios where extending selectors are helpful and worthwhile. Always keep in mind these rules so you can @extend with care:

  • Use extend on %placeholders primarily, not on actual selectors.
  • When extending classes, only extend a class with another class, never a complex selector.
  • Directly extend a %placeholder as few times as possible.
  • Avoid extending general ancestor selectors (e.g. .foo .bar) or general sibling selectors (e.g. .foo ~ .bar). This is what causes selector explosion.

It is often said that @extend helps with the file size since it combines selectors rather than duplicating properties. That is true, however the difference is negligible once Gzip has done its compression.

That being said, if you cannot use Gzip (or any equivalent) then switching to a @extend approach might be valuable, especially if stylesheet weight is your performance bottleneck.

Extend And Media Queries

You should only extend selectors within the same media scope (@mediadirective). Think of a media query as another constraint.

%foo {
  content: 'foo';
}

// Nope
@media print {
  .bar {
    // This doesn't work. Worse: it crashes.
    @extend %foo;
  }
}

// Yep
@media print {
  .bar {
    @at-root (without: media) {
      @extend %foo;
    }
  }
}

// Yep
%foo {
  content: 'foo';

  &-print {
    @media print {
      content: 'foo print';
    }
  }
}

@media print {
  .bar {
    @extend %foo-print;
  }
}

Opinions seem to be extremely divided regarding the benefits and problems from @extend to the point where many developers including myself have been advocating against it, as you can read in the following articles:

That being said and to sum up, I would advise to use @extend only for maintaining relationships within selectors. If two selectors are characteristically similar, that is the perfect use-case for @extend. If they are unrelated but share some rules, a @mixin might suit you better. More on how to choose between the two in this write-up.

Thanks to David Khourshid for his help and expertise on this section.

Mixins

Mixins are one of the most used features from the whole Sass language. They are the key to reusability and DRY components. And for good reason: mixins allow authors to define styles that can be reused throughout the stylesheet without needing to resort to non-semantic classes such as .float-left.

They can contain full CSS rules and pretty much everything that is allowed anywhere in a Sass document. They can even take arguments, just like functions. Needless to say, the possibilities are endless.

But I feel I must warn you against abusing the power of mixins. Again, the keyword here is simplicity. It might be tempting to build extremely powerful mixins with massive amounts of logic. It’s called over-engineering and most developers suffer from it. Don’t over think your code, and above all keep it simple. If a mixin ends up being longer than 20 lines or so, then it should be either split into smaller chunks or completely revised.

Basics

That being said, mixins are extremely useful and you should be using some. The rule of thumb is that if you happen to spot a group of CSS properties that always appear together for a reason (i.e. not a coincidence), you can put them in a mixin instead. The micro-clearfix hack from Nicolas Gallagher deserves to be put in a (argumentless) mixin for instance.

/// Helper to clear inner floats
/// @author Nicolas Gallagher
/// @link http://nicolasgallagher.com/micro-clearfix-hack/ Micro Clearfix
@mixin clearfix {
  &::after {
    content: '';
    display: table;
    clear: both;
  }
}

Another valid example would be a mixin to size an element, defining both width and height at the same time. Not only would it make the code lighter to type, but also easier to read.

/// Helper to size an element
/// @author Hugo Giraudel
/// @param {Length} $width
/// @param {Length} $height
@mixin size($width, $height: $width) {
  width: $width;
  height: $height;
}

For more complex examples of mixins, have a look at this mixin to generate CSS triangles, this mixin to create long shadows or this mixin to polyfill CSS gradients for old browsers.

Argument-Less Mixins

Sometimes mixins are used only to avoid repeating the same group of declarations over and over again, yet do not need any parameter or have sensible enough defaults so that we don’t necessarily have to pass arguments.

In such cases, we can safely omit the parentheses when calling them. The @include keyword (or + sign in indented-syntax) already acts as a indicator that the line is a mixin call; there is no need for extra parentheses here.

// Yep
.foo {
  @include center;
}

// Nope
.foo {
  @include center();
}

Arguments List

When dealing with an unknown number of arguments in a mixin, always use an arglist rather than a list. Think of arglist as the 8th hidden undocumented data type from Sass that is implicitly used when passing an arbitrary number of arguments to a mixin or a function whose signature contains ....

@mixin shadows($shadows...) {
  // type-of($shadows) == 'arglist'
  // …
}

Now, when building a mixin that accepts a handful of arguments (understand 3 or more), think twice before merging them out as a list or a map thinking it will be easier than passing them all one by one.

Sass is actually pretty clever with mixins and function declarations, so much so that you can actually pass a list or a map as an arglist to a function/mixin so that it gets parsed as a series of arguments.

@mixin dummy($a, $b, $c) {
  // …
}

// Yep
@include dummy(true, 42, 'kittens');

// Yep but nope
$params: (true, 42, 'kittens');
$value: dummy(nth($params, 1), nth($params, 2), nth($params, 3));

// Yep
$params: (true, 42, 'kittens');
@include dummy($params...);

// Yep
$params: (
  'c': 'kittens',
  'a': true,
  'b': 42,
);
@include dummy($params...);

For more information on whether it is best to use multiple arguments, a list or an argument list, SitePoint has a nice piece on the topic.

Mixins And Vendor Prefixes

It might be tempting to define custom mixins to handle vendor prefixes for unsupported or partially supported CSS properties. But we do not want to do this. First, if you can use Autoprefixer, use Autoprefixer. It will remove Sass code from your project, will always be up-to-date and will necessarily do a much better job than you at prefixing stuff.

Unfortunately, Autoprefixer is not always an option. If you use either Bourbon or Compass, you may already know that they both provide a collection of mixins that handle vendor prefixes for you. Use those.

If you cannot use Autoprefixer and use neither Bourbon nor Compass, then and only then, you can have your own mixin for prefixing CSS properties. But. Please do not build a mixin per property, manually printing each vendor.

// Nope
@mixin transform($value) {
  -webkit-transform: $value;
  -moz-transform: $value;
  transform: $value;
}

Do it the clever way.

/// Mixin helper to output vendor prefixes
/// @access public
/// @author HugoGiraudel
/// @param {String} $property - Unprefixed CSS property
/// @param {*} $value - Raw CSS value
/// @param {List} $prefixes - List of prefixes to output
@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
    -#{$prefix}-#{$property}: $value;
  }

  #{$property}: $value;
}

Then using this mixin should be very straightforward:

.foo {
  @include prefix(transform, rotate(90deg), ('webkit', 'ms'));
}

Please keep in mind this is a poor solution. For instance, it cannot deal with complex polyfills such as those required for Flexbox. In that sense, using Autoprefixer would be a far better option.

Conditional Statements

You probably already know that Sass provides conditional statements via the @if and @else directives. Unless you have some medium to complex logic in your code, there is no need for conditional statements in your everyday stylesheets. Actually, they mainly exist for libraries and frameworks.

Anyway, if you ever find yourself in need of them, please respect the following guidelines:

  • No parentheses unless they are necessary;
  • Always an empty new line before @if;
  • Always a line break after the opening brace ({);
  • @else statements on the same line as previous closing brace (}).
  • Always an empty new line after the last closing brace (}) unless the next line is a closing brace (}).
// Yep
@if $support-legacy {
  // …
} @else {
  // …
}

// Nope
@if ($support-legacy == true) {
  // …
}
@else {
  // …
}

When testing for a falsy value, always use the not keyword rather than testing against false or null.

// Yep
@if not index($list, $item) {
  // …
}

// Nope
@if index($list, $item) == null {
  // …
}

Always put the variable part on the left side of the statement, and the (un)expected result on the right. Reversed conditional statements often are more difficult to read, especially to unexperienced developers.

// Yep
@if $value == 42 {
  // …
}

// Nope
@if 42 == $value {
  // …
}

When using conditional statements within a function to return a different result based on some condition, always make sure the function still has a @return statement outside of any conditional block.

// Yep
@function dummy($condition) {
  @if $condition {
    @return true;
  }

  @return false;
}

// Nope
@function dummy($condition) {
  @if $condition {
    @return true;
  } @else {
    @return false;
  }
}

Loops

Because Sass provides complex data structures such as lists and maps, it is no surprise that it also gives a way for authors to iterate over those entities.

However, the presence of loops usually implies moderately complex logic that probably does not belong to Sass. Before using a loop, make sure it makes sense and that it actually solves an issue.

Each

The @each loop is definitely the most-used out of the three loops provided by Sass. It provides a clean API to iterate over a list or a map.

@each $theme in $themes {
  .section-#{$theme} {
    background-color: map-get($colors, $theme);
  }
}

When iterating on a map, always use $key and $value as variable names to enforce consistency.

@each $key, $value in $map {
  .section-#{$key} {
    background-color: $value;
  }
}

Also be sure to respect those guidelines to preserve readability:

  • Always an empty new line before @each;
  • Always an empty new line after the closing brace (}) unless the next line is a closing brace (}).

 

Learning extend is a bit challenging at first but as i gone through various other tutorials including the official SASS documentations. In short summary for extend , its basically a way to do inheritance in SASS i.e it will inherit the styles from the selectors.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s