Tag: Sass

Introducing Sass Modules

Sass just launched a major new feature you might recognize from other languages: a module system. This is a big step forward for @import. one of the most-used Sass-features. While the current @import rule allows you to pull in third-party packages, and split your Sass into manageable “partials,” it has a few limitations:

  • @import is also a CSS feature, and the differences can be confusing
  • If you @import the same file multiple times, it can slow down compilation, cause override conflicts, and generate duplicate output.
  • Everything is in the global namespace, including third-party packages – so my color() function might override your existing color() function, or vice versa.
  • When you use a function like color(). it’s impossible to know exactly where it was defined. Which @import does it come from?

Sass package authors (like me) have tried to work around the namespace issues by manually prefixing our variables and functions — but Sass modules are a much more powerful solution. In brief, @import is being replaced with more explicit @use and @forward rules. Over the next few years Sass @import will be deprecated, and then removed. You can still use CSS imports, but they won’t be compiled by Sass. Don’t worry, there’s a migration tool to help you upgrade!

Import files with @use

@use 'buttons';

The new @use is similar to @import. but has some notable differences:

  • The file is only imported once, no matter how many times you @use it in a project.
  • Variables, mixins, and functions (what Sass calls “members”) that start with an underscore (_) or hyphen (-) are considered private, and not imported.
  • Members from the used file (buttons.scss in this case) are only made available locally, but not passed along to future imports.
  • Similarly, @extends will only apply up the chain; extending selectors in imported files, but not extending files that import this one.
  • All imported members are namespaced by default.

When we @use a file, Sass automatically generates a namespace based on the file name:

@use 'buttons'; // creates a `buttons` namespace @use 'forms'; // creates a `forms` namespace

We now have access to members from both buttons.scss and forms.scss — but that access is not transferred between the imports: forms.scss still has no access to the variables defined in buttons.scss. Because the imported features are namespaced, we have to use a new period-divided syntax to access them:

// variables: <namespace>.$ variable $ btn-color: buttons.$ color; $ form-border: forms.$ input-border;  // functions: <namespace>.function() $ btn-background: buttons.background(); $ form-border: forms.border();  // mixins: @include <namespace>.mixin() @include buttons.submit(); @include forms.input();

We can change or remove the default namespace by adding as <name> to the import:

@use 'buttons' as *; // the star removes any namespace @use 'forms' as 'f';  $ btn-color: $ color; // buttons.$ color without a namespace $ form-border: f.$ input-border; // forms.$ input-border with a custom namespace

Using as * adds a module to the root namespace, so no prefix is required, but those members are still locally scoped to the current document.

Import built-in Sass modules

Internal Sass features have also moved into the module system, so we have complete control over the global namespace. There are several built-in modules — math, color, string, list, map, selector, and meta — which have to be imported explicitly in a file before they are used:

@use 'sass:math'; $ half: math.percentage(1/2);

Sass modules can also be imported to the global namespace:

@use 'sass:math' as *; $ half: percentage(1/2);

Internal functions that already had prefixed names, like map-get or str-index. can be used without duplicating that prefix:

@use 'sass:map'; @use 'sass:string'; $ map-get: map.get(('key': 'value'), 'key'); $ str-index: string.index('string', 'i');

You can find a full list of built-in modules, functions, and name changes in the Sass module specification.

New and changed core features

As a side benefit, this means that Sass can safely add new internal mixins and functions without causing name conflicts. The most exciting example in this release is a sass:meta mixin called load-css(). This works similar to @use but it only returns generated CSS output, and it can be used dynamically anywhere in our code:

@use 'sass:meta'; $ theme-name: 'dark';  [data-theme='#{$ theme-name}'] {   @include meta.load-css($ theme-name); }

The first argument is a module URL (like @use) but it can be dynamically changed by variables, and even include interpolation, like theme-#{$ name}. The second (optional) argument accepts a map of configuration values:

// Configure the $ base-color variable in 'theme/dark' before loading @include meta.load-css(   'theme/dark',    $ with: ('base-color': rebeccapurple) );

The $ with argument accepts configuration keys and values for any variable in the loaded module, if it is both:

  • A global variable that doesn’t start with _ or - (now used to signify privacy)
  • Marked as a !default value, to be configured
// theme/_dark.scss $ base-color: black !default; // available for configuration $ _private: true !default; // not available because private $ config: false; // not available because not marked as a !default

Note that the 'base-color' key will set the $ base-color variable.

There are two more sass:meta functions that are new: module-variables() and module-functions(). Each returns a map of member names and values from an already-imported module. These accept a single argument matching the module namespace:

@use 'forms';  $ form-vars: module-variables('forms'); // ( //   button-color: blue, //   input-border: thin, // )  $ form-functions: module-functions('forms'); // ( //   background: get-function('background'), //   border: get-function('border'), // )

Several other sass:meta functions — global-variable-exists(), function-exists(), mixin-exists(), and get-function() — will get additional $ module arguments, allowing us to inspect each namespace explicitly.

Adjusting and scaling colors

The sass:color module also has some interesting caveats, as we try to move away from some legacy issues. Many of the legacy shortcuts like lighten(). or adjust-hue() are deprecated for now in favor of explicit color.adjust() and color.scale() functions:

// previously lighten(red, 20%) $ light-red: color.adjust(red, $ lightness: 20%);  // previously adjust-hue(red, 180deg) $ complement: color.adjust(red, $ hue: 180deg);

Some of those old functions (like adjust-hue) are redundant and unnecessary. Others — like lighten. darken. saturate. and so on — need to be re-built with better internal logic. The original functions were based on adjust(). which uses linear math: adding 20% to the current lightness of red in our example above. In most cases, we actually want to scale() the lightness by a percentage, relative to the current value:

// 20% of the distance to white, rather than current-lightness + 20 $ light-red: color.scale(red, $ lightness: 20%);

Once fully deprecated and removed, these shortcut functions will eventually re-appear in sass:color with new behavior based on color.scale() rather than color.adjust(). This is happening in stages to avoid sudden backwards-breaking changes. In the meantime, I recommend manually checking your code to see where color.scale() might work better for you.

Configure imported libraries

Third-party or re-usable libraries will often come with default global configuration variables for you to override. We used to do that with variables before an import:

// _buttons.scss $ color: blue !default;  // old.scss $ color: red; @import 'buttons';

Since used modules no longer have access to local variables, we need a new way to set those defaults. We can do that by adding a configuration map to @use:

@use 'buttons' with (   $ color: red,   $ style: 'flat', );

This is similar to the $ with argument in load-css(). but rather than using variable-names as keys, we use the variable itself, starting with $ .

I love how explicit this makes configuration, but there’s one rule that has tripped me up several times: a module can only be configured once, the first time it is used. Import order has always been important for Sass, even with @import. but those issues always failed silently. Now we get an explicit error, which is both good and sometimes surprising. Make sure to @use and configure libraries first thing in any “entrypoint” file (the central document that imports all partials), so that those configurations compile before other @use of the libraries.

It’s (currently) impossible to “chain” configurations together while keeping them editable, but you can wrap a configured module along with extensions, and pass that along as a new module.

Pass along files with @forward

We don’t always need to use a file, and access its members. Sometimes we just want to pass it along to future imports. Let’s say we have multiple form-related partials, and we want to import all of them together as one namespace. We can do that with @forward:

// forms/_index.scss @forward 'input'; @forward 'textarea'; @forward 'select'; @forward 'buttons';

Members of the forwarded files are not available in the current document and no namespace is created, but those variables, functions, and mixins will be available when another file wants to @use or @forward the entire collection. If the forwarded partials contain actual CSS, that will also be passed along without generating output until the package is used. At that point it will all be treated as a single module with a single namespace:

// styles.scss @use 'forms'; // imports all of the forwarded members in the `forms` namespace

Note: if you ask Sass to import a directory, it will look for a file named index or _index)

By default, all public members will forward with a module. But we can be more selective by adding show or hide clauses, and naming specific members to include or exclude:

// forward only the 'input' border() mixin, and $ border-color variable @forward 'input' show border, $ border-color;  // forward all 'buttons' members *except* the gradient() function @forward 'buttons' hide gradient;

Note: when functions and mixins share a name, they are shown and hidden together.

In order to clarify source, or avoid naming conflicts between forwarded modules, we can use as to prefix members of a partial as we forward:

// forms/_index.scss // @forward "<url>" as <prefix>-*; // assume both modules include a background() mixin @forward 'input' as input-*; @forward 'buttons' as btn-*;  // style.scss @use 'forms'; @include forms.input-background(); @include forms.btn-background();

And, if we need, we can always @use and @forward the same module by adding both rules:

@forward 'forms'; @use 'forms';

That’s particularly useful if you want to wrap a library with configuration or any additional tools, before passing it along to your other files. It can even help simplify import paths:

// _tools.scss // only use the library once, with configuration @use 'accoutrement/sass/tools' with (   $ font-path: '../fonts/', ); // forward the configured library with this partial @forward 'accoutrement/sass/tools';  // add any extensions here...   // _anywhere-else.scss // import the wrapped-and-extended library, already configured @use 'tools';

Both @use and @forward must be declared at the root of the document (not nested), and at the start of the file. Only @charset and simple variable definitions can appear before the import commands.

Moving to modules

In order to test the new syntax, I built a new open source Sass library (Cascading Color Systems) and a new website for my band — both still under construction. I wanted to understand modules as both a library and website author. Let’s start with the “end user” experience of writing site styles with the module syntax…

Maintaining and writing styles

Using modules on the website was a pleasure. The new syntax encourages a code architecture that I already use. All my global configuration and tool imports live in a single directory (I call it config), with an index file that forwards everything I need:

// config/_index.scss @forward 'tools'; @forward 'fonts'; @forward 'scale'; @forward 'colors';

As I build out other aspects of the site, I can import those tools and configurations wherever I need them:

// layout/_banner.scss @use '../config';  .page-title {   @include config.font-family('header'); }

This even works with my existing Sass libraries, like Accoutrement and Herman, that still use the old @import syntax. Since the @import rule will not be replaced everywhere overnight, Sass has built in a transition period. Modules are available now, but @import will not be deprecated for another year or two — and only removed from the language a year after that. In the meantime, the two systems will work together in either direction:

  • If we @import a file that contains the new @use/@forward syntax, only the public members are imported, without namespace.
  • If we @use or @forward a file that contains legacy @import syntax, we get access to all the nested imports as a single namespace.

That means you can start using the new module syntax right away, without waiting for a new release of your favorite libraries: and I can take some time to update all my libraries!

Migration tool

Upgrading shouldn’t take long if we use the Migration Tool built by Jennifer Thakar. It can be installed with Node, Chocolatey, or Homebrew:

npm install -g sass-migrator choco install sass-migrator brew install sass/sass/migrator

This is not a single-use tool for migrating to modules. Now that Sass is back in active development (see below), the migration tool will also get regular updates to help migrate each new feature. It’s a good idea to install this globally, and keep it around for future use.

The migrator can be run from the command line, and will hopefully be added to third-party applications like CodeKit and Scout as well. Point it at a single Sass file, like style.scss. and tell it what migration(s) to apply. At this point there’s only one migration called module:

# sass-migrator <migration> <entrypoint.scss...> sass-migrator module style.scss

By default, the migrator will only update a single file, but in most cases we’ll want to update the main file and all its dependencies: any partials that are imported, forwarded, or used. We can do that by mentioning each file individually, or by adding the --migrate-deps flag:

sass-migrator --migrate-deps module style.scss

For a test-run, we can add --dry-run --verbose (or -nv for short), and see the results without changing any files. There are a number of other options that we can use to customize the migration — even one specifically for helping library authors remove old manual namespaces — but I won’t cover all of them here. The migration tool is fully documented on the Sass website.

Updating published libraries

I ran into a few issues on the library side, specifically trying to make user-configurations available across multiple files, and working around the missing chained-configurations. The ordering errors can be difficult to debug, but the results are worth the effort, and I think we’ll see some additional patches coming soon. I still have to experiment with the migration tool on complex packages, and possibly write a follow-up post for library authors.

The important thing to know right now is that Sass has us covered during the transition period. Not only can imports and modules work together, but we can create “import-only” files to provide a better experience for legacy users still @importing our libraries. In most cases, this will be an alternative version of the main package file, and you’ll want them side-by-side: <name>.scss for module users, and <name>.import.scss for legacy users. Any time a user calls @import <name>, it will load the .import version of the file:

// load _forms.scss @use 'forms';  // load _forms.input.scss @import 'forms';

This is particularly useful for adding prefixes for non-module users:

// _forms.import.scss // Forward the main module, while adding a prefix @forward "forms" as forms-*;

Upgrading Sass

You may remember that Sass had a feature-freeze a few years back, to get various implementations (LibSass, Node Sass, Dart Sass) all caught up, and eventually retired the original Ruby implementation. That freeze ended last year, with several new features and active discussions and development on GitHub – but not much fanfare. If you missed those releases, you can get caught up on the Sass Blog:

Dart Sass is now the canonical implementation, and will generally be the first to implement new features. If you want the latest, I recommend making the switch. You can install Dart Sass with Node, Chocolatey, or Homebrew. It also works great with existing gulp-sass build steps.

Much like CSS (since CSS3), there is no longer a single unified version-number for new releases. All Sass implementations are working from the same specification, but each one has a unique release schedule and numbering, reflected with support information in the beautiful new documentation designed by Jina.

Sass Modules are available as of October 1st, 2019 in Dart Sass 1.23.0.

The post Introducing Sass Modules appeared first on CSS-Tricks.

CSS-Tricks

, ,

Do CSS Custom Properties Beat Sass Loops?

I reckon that a lot of our uses of Sass maps can be replaced with CSS Custom properties – but hear me out for a sec.

When designing components we often need to use the same structure of a component but change its background or text color based on a theme. For example, in an alert, we might need a warning style, an error style, and a success style – each of which might be slightly different, like this:

There’s a few ways we could tackle building this with CSS, and if you were asking me a couple of years ago, I would’ve tried to solve this problem with Sass maps. First, I would have started with the base alert styles but then I’d make a map that would hold all the data:

$ alertStyles: (   error: (     theme: #fff5f5,     icon: 'error.svg',     darkTheme: #f78b8b   ),   success: (     theme: #f0f9ef,     icon: 'success.svg',     darkTheme: #7ebb7a   ),   warning: (     theme: #fff9f0,     icon: 'warning.svg',     darkTheme: #ffc848   ) );

Then we can loop through that data to change our core alert styles, like this:

@each $ state, $ property in $ alertStyles {   $ theme: map-get($ property, theme);   $ darkTheme: map-get($ property, darkTheme);   $ icon: map-get($ property, icon);      .alert-#{$ state} {     background-color: $ theme;     border-color: $ darkTheme;       &:before {       background-color: $ darkTheme;       background-image: url($ icon);     }     .alert-title {       color: $ darkTheme;     }   } }

Pretty complicated, huh? This would output classes such as .alert-error, .alert-success and .alert-warning, each of which would have a bunch of CSS within them that overrides the default alert styles.

This would leave us with something that looks like this demo:

See the Pen
Alerts – Sass Loops
by Robin Rendle (@robinrendle)
on CodePen.

However! I’ve always found that using Sass maps and looping over all this data can become unwieldy and extraordinarily difficult to read. In recent projects, I’ve stumbled into fantastically complicated uses of maps and slowly closed the file as if I’d stumbled into a crime scene.

How do we keep the code easy and legible? Well, I think that CSS Custom Properties makes these kinds of loops much easier to read and therefore easier to edit and refactor in the future.

Let’s take the example above and refactor it so that it uses CSS Custom Properties instead. First we’ll set out core styles for the .alert component like so:

See the Pen
Alerts – Custom Variables 1
by Robin Rendle (@robinrendle)
on CodePen.

As we create those base styles, we can setup variables in our .alert class like this:

.alert {   --theme: #ccc;   --darkTheme: #777;   --icon: '';   background: var(--theme);   border: 1px solid var(--darkTheme);   /* other styles go here */      &:before {     background-image: var(--icon);   } }

We can do a lot more with CSS Custom Properties than changing an interface to a dark mode or theme. I didn’t know until I tried that it’s possible to set an image in a custom property like that – I simply assumed it was for hex values.

Anyway! From there, we can style each custom .alert class like .alert-warning by overriding these properties in .alert:

.alert-success {   --theme: #f0f9ef;   --darkTheme: #7ebb7a;   --icon: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/14179/success.svg); }  .alert-error {   --theme: #fff5f5;   --darkTheme: #f78b8b;   --icon: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/14179/error.svg); }  .alert-warning {   --theme: #fff9f0;    --darkTheme: #ffc848;   --icon: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/14179/warning.svg); } 

And that’s about it! We’ll get the exact same visual interface that we had with a Sass loop:

See the Pen
Alerts – Custom Variables 2
by Robin Rendle (@robinrendle)
on CodePen.

However! I think there’s an enormous improvement here that’s been made in terms of legibility. It’s much easier to look at this code and to understand it right off the bat. With the Sass loop it almost seems like we are trying to do a lot of clever things in one place – namely, nest classes within other classes and create the class names themselves. Not to mention we then have to go back and forth between the original Sass map and our styles.

With CSS Custom Properties, all the styles are contained within the original .alert.

There you have it! I think there’s not much to mention here besides the fact that CSS Custom Properties can make code more legible and maintainable in the future. And I reckon that’s something we should all be a little excited about.

Although there is one last thing: we should probably be aware of browser support whilst working with Custom Properties although it’s pretty good across the board.

The post Do CSS Custom Properties Beat Sass Loops? appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Where Do You Nest Your Sass Breakpoints?

I love nesting my @media query breakpoints. It’s perhaps the most important feature of Sass to me. Maybe I pick a method and do it like this:

.element {   display: grid;   grid-template-columns: 100px 1fr;   @include breakpoint(baby-bear) {     display: block;   } }

That’s straightforward enough. But what if my element has several sub-elements and the breakpoint affects them as well? There are different approaches, and I’m never quite sure which one I should be doing.

I could duplicate the breakpoint for each child:

.parent {      @include breakpoint(desktop) {     }      .child {         @include breakpoint(desktop) {         }     }     .child-2 {         @include breakpoint(desktop) {         }     }  }

The compiled CSS comes out to something like this:

@media screen and (min-width: 700px) {   .parent {   } } @media screen and (min-width: 700px) {   .parent .child {   } } @media screen and (min-width: 700px) {   .parent .child-2 {   } }

Or, I could duplicate the children under the first nested breakpoint:

.parent {      @include breakpoint(desktop) {         .child {        }         .child-2 {        }       }      .child {     }     .child-2 {     }  }

That results in:

@media screen and (min-width: 700px) {   .parent .child {   }   .parent .child-2 {   } } .parent .child { } .parent .child-2 { }

Or I could do a combination of the two. Neither of them feels particularly great because of the duplication, but I’m not sure there is a perfect answer here. I err a little more on duplicating the media query, as it seems less error-prone than duplicating selectors.

The post Where Do You Nest Your Sass Breakpoints? appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Sass Techniques from the Trenches

Having been in the web development industry for more than 14 years, I’ve seen and written my fair share of good and bad CSS. When I began at Ramsey Solutions five years ago, I was introduced to Sass. It blew my mind how useful it was! I dove right in and wanted to learn everything I could about it. Over the past five years, I’ve utilized a number of different Sass techniques and patterns and fell in love with some that, to steal Apple’s phrase, just work.

In this article, I’ll explore a wide range of topics:

In my experience, finding the balance between simple and complex is the crucial component to making great software. Software should not only be easy for people to use, but for you and other developers to maintain in the future. I’d consider these techniques to be advanced, but not necessarily clever or complex, on purpose!

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?”

—The Elements of Programming and Style (2nd Edition), Chapter 2

With that in mind, let’s first look at Sass’ ampersand.


The power of the ampersand

There are many different naming conventions you can use to organize your CSS. The one I enjoy using the most is SUIT, a variation of BEM (which is short for Block, Element, Modifier). If you’re unfamiliar with SUIT or BEM, I’d recommend taking a peek at one or both of them before moving on. I’ll be using the SUIT convention throughout the rest of this article.

Whatever naming convention you choose, the base idea is that every styled element gets its own class name, prepended with the component name. This idea is important for how some of the following organization works. Also, this article is descriptive, not prescriptive. Every project is different. You need to do what works best for your project and your team.

The ampersand is the main reason I like to use SUIT, BEM, and conventions like them. It allows me to use nesting and scoping without either biting back with specificity. Here’s an example. Without using the ampersand, I would need to create separate selectors to create -title and -content elements.

.MyComponent {   .MyComponent-title {} }  .MyComponent-content {}  // Compiles to .MyComponent .MyComponent-title {} // Not what we want. Unnecessary specificity! .MyComponent-content {} // Desired result

When using SUIT, I want the second result for -content to be how I write all my selectors. To do so, I would need to repeat the name of the component throughout. This increases my chance to mistype the name of the component as I write new styles. It’s also very noisy as it ends up ignoring the beginning of many selectors which can lead to glossing over obvious errors.

.MyComponent {} .MyComponent-title {} .MyComponent-content {} .MyComponent-author {} // Etc.

If this were normal CSS, we’d be stuck writing the above. Since we’re using Sass, there’s a much better approach using the ampersand. The ampersand is amazing because it contains a reference to the current selector along with any parents.

.A {   // & = '.A'   .B {     // & = '.A .B'     .C {       // & = '.A .B .C'     }   } }

You can see in the above example how the ampersand references each selector in the chain as it goes deeper into the nested code. By utilizing this feature, we can create new selectors without having to rewrite the name of the component each and every time.

.MyComponent {   &-title {}      &-content {} }  // Compiles to .MyComponent {} .MyComponent-title {} .MyComponent-content {}

This is great because we can take advantage of the ampersand to write the name of the component one time and simply reference the component name throughout. This decreases the chance that the component name is mistyped. Plus, the document as a whole becomes easier to read without .MyComponent repeated all over the code.


There are times when the component needs a variant or modifier, as they’re called in SUIT and BEM. Using the ampersand pattern makes it easier to create modifiers.

<div class="MyComponent MyComponent--xmasTheme"></div>
.MyComponent {   &--xmasTheme {} }  // Compiles to .MyComponent {} .MyComponent--xmasTheme {}

“But, what about modifying the child elements?” you might ask. “How are those selectors created? The modifier isn’t needed on every element, right?”

This is where variables can help!

Variables and scoping

In the past, I’ve created modifiers a few different ways. Most of the time, I’d rewrite the special theme name I want to apply when modifying the element.

.MyComponent {   &-title {     .MyComponent--xmasTheme & {     }   }      &-content {     .MyComponent--xmasTheme & {     }   } }  // Compiles to .MyComponent-title {} .MyComponent--xmasTheme .MyComponent-title {} .MyComponent-content {} .MyComponent--xmasTheme .MyComponent-content {}

This gets the job done, but I’m back to rewriting the component name in multiple places, not to mention the modifier name. There’s definitely a better way to do this. Enter Sass variables.

Before we explore Sass variables with selectors, we need to understand how they’re scoped. Sass variables have scope, just like they would in JavaScript, Ruby, or any other programming language. If declared outside of a selector, the variable is available to every selector in the document after its declaration.

$ fontSize: 1.4rem;  .a { font-size: $ fontSize; } .b { font-size: $ fontSize; }

Variables declared inside a selector are scoped only to that selector and its children.

$ fontSize: 1.4rem;  .MyComponent {    $ fontWeight: 600;   font-size: $ fontSize;       &-title {     font-weight: $ fontWeight; // Works!   } }  .MyComponent2 {    font-size: $ fontSize;       &-title {     font-weight: $ fontWeight; // produces an "undefined variable" error   } }

We know variables can store font names, integers, colors, etc. Did you know it can also store selectors? Using string interpolation, we can create new selectors with the variable.

// Sass string interpolation syntax is #{VARIABLE}  $ block: ".MyComponent";  #{$ block} {   &-title {     #{$ block}--xmasTheme & {     }   } }  // Compiles to .MyComponent {} .MyComponent-title {} .MyComponent--xmasTheme .MyComponent-title {}

That’s cool, but the variable is globally scoped. We can fix that by creating the $ block variable inside the component declaration, which would scope it to that component. Then we can re-use the $ block variable in other components. This helps DRY up the theme modifier.

.MyComponent {   $ block: '.MyComponent';      &-title {     #{$ block}--xmasTheme & {     }   }      &-content {     #{$ block}--xmasTheme & {     }   } }  // Compiles to .MyComponent {} .MyComponent-title {} .MyComponent--xmasTheme .MyComponent-title {} .MyComponent-content {} .MyComponent--xmasTheme .MyComponent-content {}

This is closer, but again, we have to write the theme name over and over. Let’s store that in a variable too!

.MyComponent {   $ block: '.MyComponent';   $ xmasTheme: '.MyComponent--xmasTheme';      &-title {     #{$ xmasTheme} & {     }   } }

This is much better! However, we can improve this even further. Variables can also store the value of the ampersand!

.MyComponent {   $ block: &;   $ xmasTheme: #{&}--xmasTheme;      &-title {     #{$ xmasTheme} & {     }   } }  // Still compiles to .MyComponent {} .MyComponent-title {} .MyComponent--xmasTheme .MyComponent-title {}

Now that’s what I’m talking about! “Caching” the selector with ampersand allows us to create our modifiers at the top and keep the theme modifications with the element it’s modifying.

“Sure, that works at the top level,” you say. “But what if you are nested really deep, like eight levels in?” You ask great questions.

No matter how deep the nest, this pattern always works because the main component name is never attached to any of the children, thanks to the SUIT naming convention and ampersand combo.

.MyComponent {    $ block: &;   $ xmasTheme: #{&}--xmasTheme;      &-content {     font-size: 1.5rem;     color: blue;          ul {       li {         strong {           span {             &::before {               background-color: blue;                              #{$ xmasTheme} & {                 background-color: red;               }             }           }         }       }     }   } }  // Compiles to  .MyComponent-content {   font-size: 1.5rem;   color: blue; }  .MyComponent-content ul li strong span::before {   background-color: blue; }  /* * The theme is still appended to the beginning of the selector! * Now, we never need to write deeply nested Sass that's hard to maintain and  * extremely brittle: https://css-tricks.com/sass-selector-combining/ */ .MyComponent--xmasTheme .MyComponent-content ul li strong span::before {   background-color: red; }

Code organization is the main reason I like to use this pattern.

  • It’s relatively DRY
  • It supports the “opt-in” approach, which keeps modifiers with the elements they modify
  • Naming stuff is hard but this enables us to reuse common element names like “title” and “content”
  • It’s low-lift to add a modifier to a component by placing the modifier class on the parent component

“Hhhmmmmm… doesn’t that get hard to read though after you create a bunch of different components? How do you know where you’re at when everything is named &-title and &-content?”

You continue to ask great questions. Who said the source Sass had to be in one file? We can import those components, so let’s turn to that topic!

The importance of imports

Credit: @Julien_He

One of Sass’ best features is @import. We can create separate Sass files (partials) and import them into other Sass files that compile together with the imported file located at the spot it’s imported. This makes it easy to package up related styles for components, utilities, etc. and pull them into a single file. Without @import, we’d need to link to separate CSS files (creating numerous network requests, which is badong) or write everything in a single stylesheet (which is tough to navigate and maintain).

.Component1 {   &-title {}   &-content {}   &-author {} }  .Component2 {   &-title {}   &-content {}   &-author {} }  .Component3 {   &-title {}   &-content {}   &-author {} }  .Component4 {   &-title {}   &-content {}   &-author {} }  .Component5 {   &-title {}   &-content {}   &-author {} }  // A couple hundred lines later...  .Component7384 {   &-title {}   &-content {}   &-author {} }  // WHERE AM I?

One of the more popular methodologies for organizing Sass files is the 7-1 Pattern. That’s seven distinct folders containing Sass files that are imported into a single Sass file.

Those folders are:

  • abstracts
  • base
  • components
  • layout
  • pages
  • themes
  • vendor

Use @import to pull each Sass file in those folder into a main Sass file. We want to import them in the following order to maintain good scope and avoid conflicts during compilation:

  1. abstracts
  2. vendor
  3. base
  4. layout
  5. components
  6. pages
  7. themes
@import 'abstracts/variables'; @import 'abstracts/functions'; @import 'abstracts/mixins';  @import 'vendors/some-third-party-component';  @import 'base/normalize';  @import 'layout/navigation'; @import 'layout/header'; @import 'layout/footer'; @import 'layout/sidebar'; @import 'layout/forms';  @import 'components/buttons'; @import 'components/hero'; @import 'components/pull-quote';  @import 'pages/home'; @import 'pages/contact';  @import 'themes/default'; @import 'themes/admin';

You may or may not want to use all of these folders (I personally don’t use the theme folder since I keep themes with their components), but the idea of separating all of styles into distinct files makes it easier to maintain and find code.

More of the benefits of using this approach:

  • Small components are easier to read and understand
  • Debugging becomes simpler
  • It’s clearer to determine when a new component should be created — like when a single component file gets to be too long, or the selector chain is too complex
  • This emphasizes re-usage — for example, it might make sense to generalize three component files that essentially do the same thing into one component

Speaking of re-usage, there are eventually patterns that get used often. That’s when we can reach for mixins.

Mixin’ it up

Mixins are a great way to reuse styles throughout a project. Let’s walk through creating a simple mixin and then give it a little bit of intelligence.

The designer I work with on a regular basis always sets font-size, font-weight, and line-height to specific values. I found myself typing all three out every time I needed to adjust the fonts for a component or element, so I created a mixin to quickly set those values. It’s like a little function I can use to define those properties without having to write them in full.

@mixin text($ size, $ lineHeight, $ weight) {   font-size: $ size;   line-height: $ lineHeight;   font-weight: $ weight; }

At this point, the mixin is pretty simple—it resembles something like a function in JavaScript. There’s the name of the mixin (text) and it takes in three arguments. Each argument is tied to a CSS property. When the mixin is called, Sass will copy the properties and the pass in the argument values.

.MyComponent {   @include text(18px, 27px, 500); }  // Compiles to .MyComponent {   font-size: 18px;   line-height: 27px;   font-weight: 500; }

While it’s a good demonstration, this particular mixin is a little limited. It assumes we always want to use the font-size, line-height, and font-weight properties when it’s called. So let’s use Sass’ if statement to help control the output.

@mixin text($ size, $ lineHeight, $ weight) {   // If the $ size argument is not empty, then output the argument   @if $ size != null {     font-size: $ size;   }      // If the $ lineHeight argument is not empty, then output the argument   @if $ lineHeight != null {     line-height: $ lineHeight;   }      // If the $ weight argument is not empty, then output the argument   @if $ weight != null {     font-weight: $ weight;   } }  .MyComponent {   @include text(12px, null, 300); }  // Compiles to .MyComponent {   font-size: 12px;   font-weight: 300; }

That’s better, but not quite there. If I try to use the mixin without using null as a parameter on the values I don’t want to use or provide, Sass will generate an error:

.MyComponent {   @include text(12px, null); // left off $ weight }  // Compiles to an error: // "Mixin text is missing argument $ weight."

To get around this, we can add default values to the parameters, allowing us to leave them off the function call. All optional parameters have to be declared after any required parameters.

// We define `null` as the default value for each argument @mixin text($ size: null, $ lineHeight: null, $ weight: null) {   @if $ size != null {     font-size: $ size;   }      @if $ lineHeight != null {     line-height: $ lineHeight;   }      @if $ weight != null {     font-weight: $ weight;   } }  .MyComponent {   &-title {     @include text(16px, 19px, 600);   }      &-author {     @include text($ weight: 800, $ size: 12px);   } }  // Compiles to .MyComponent-title {   font-size: 16px;   line-height: 19px;   font-weight: 600; }  .MyComponent-author {   font-size: 12px;   font-weight: 800; }

Not only do default argument values make the mixin easier to use, but we also gain the ability to name parameters and give them values that may be commonly used. On Line 21 above, the mixin is being called with the arguments out of order, but since the values are being called out as well, the mixin knows how to apply them.


There’s a particular mixin that I use on a daily basis: min-width. I prefer to create all my sites mobile first, or basically with the smallest viewport in mind. As the viewport grows wider, I define breakpoints to adjust the layout and the code for it. This is where I reach for the min-width mixin.

// Let's name this "min-width" and take a single argument we can // use to define the viewport width in a media query. @mixin min-width($ threshold) {   // We're calling another function (scut-rem) to convert pixels to rem units.   // We'll cover that in the next section.   @media screen and (min-width: scut-rem($ threshold)) {     @content;   } }  .MyComponent {   display: block;      // Call the min-width mixin and pass 768 as the argument.   // min-width passes 768 and scut-rem converts the unit.   @include min-width(768) {     display: flex;   } }  // Compiles to  .MyComponent {   display: block; }  @media screen and (min-width: 48rem) {   .MyComponent {     display: flex;   } }

There are a couple of new ideas here. The mixin has a nested function called @content. So, in the .MyComponent class, we’re no longer calling the mixin alone, but also a block of code that gets output inside the media query that’s generated. The resulting code will compile where @content is called. This allows the mixin to take care of the @media declaration and still accept custom code for that particular breakpoint.

I also am including the mixin within the .MyComponent declaration. Some people advocate keeping all responsive calls in a separate stylesheet to reduce the amount of times @media is written out in a stylesheet. Personally, I prefer to keep all variations and changes that a component can go through with that component’s declaration. It tends to make it easier to keep track of what’s going on and help debug the component if something doesn’t go right, rather than sifting through multiple files.

Did you notice the scut-rem function in there? That is a Sass function taken from a Sass library called Scut, created by David The Clark. Let’s take a look at how that works.

Getting functional

A function differs from a mixin in that mixins are meant to output common groups of properties, while a function modifies properties based on arguments that return a new result. In this case, scut-rem takes a pixel value and converts it to a rem value. This allows us to think in pixels, while working with rem units behind the scenes to avoid all that math.

I’ve simplified scut-rem in this example because it has a few extra features that utilize loops and lists, which are out of the scope of what we’re covering here. Let’s look at the function in its entirety, then break it down step-by-step.

// Simplified from the original source $ scut-rem-base: 16 !default;  @function scut-strip-unit ($ num) {   @return $ num / ($ num * 0 + 1); }  @function scut-rem ($ pixels) {   @return scut-strip-unit($ pixels) / $ scut-rem-base * 1rem; }  .MyComponent {   font-size: scut-rem(18px);   }  // Compiles to .MyComponent {   font-size: 1.125rem; }

The first thing to note is the declaration on Line 2. It’s using !default when declaring a variable, which tells Sass to set the value to 16 unless this variable is already defined. So if a variable is declared earlier in the stylesheet with a different value, it won’t be overridden here.

$ fontSize: 16px; $ fontSize: 12px !default;  .MyComponent {   font-size: $ fontSize; }  // Compiles to .MyComponent {   font-size: 16px; }

The next piece of the puzzle is scut-strip-unit. This function takes a px, rem, percent or other suffixed value and removes the unit label. Calling scut-strip-unit(12px) returns 12 instead of 12px. How does that work? In Sass, a unit divided by another unit of the same type will strip the unit and return the digit.

12px / 1px = 12

Now that we know that, let’s look at the scut-strip-unit function again.

@function scut-strip-unit ($ num) {   @return $ num / ($ num * 0 + 1); }

The function takes in a unit and divides it by 1 of the same unit. So if we pass in 12px, the function would look like: @return 12px / (12px * 0 + 1). Following the order of operations, Sass evaluates what’s in the parentheses first. Sass smartly ignores the px label, evaluates the expression, and tacks px back on once it’s done: 12 * 0 + 1 = 1px. The equation is now 12px / 1px which we know returns 12.

Why is this important to scut-rem? Looks look at it again.

$ scut-rem-base: 16 !default;  @function scut-rem ($ pixels) {   @return scut-strip-unit($ pixels) / $ scut-rem-base * 1rem; }  .MyComponent {   font-size: scut-rem(18px);   }

On Line 4, the scut-strip-unit function removes px from the argument and returns 18. The base variable is equal to 16 which turns the equation into: 18 / 16 * 1rem. Remember, Sass ignores any unit until the end of the equation, so 18 / 16 = 1.125. That result multiplied by 1rem gives us 1.125rem. Since Scut strips the unit off of the argument, we can call scut-rem with unit-less values, like scut-rem(18).

I don’t write that many functions because I try to keep the stuff I create as simple as possible. Being able to do some complex conversions using something like scut-rem is helpful though.

The selector order that placeholders mess up

End up where I think it did, that CSS?

I really don’t like to use placeholders and @extend in my code. I find it easy to get in trouble with them for a couple different reasons.

Be careful what is extended

I tried writing out some examples to demonstrate why using @extend can be problematic, but I have used them so little that I can’t create any decent examples. When I first learned Sass, I was surrounded by teammates who’ve already gone through the trials and tribulations. My friend Jon Bebee wrote an extremely excellent article on how @extend can get you into trouble. It’s a quick read and worth the time, so I’ll wait.

About those placeholders…

Jon proposes using placeholders as a solution to the problem he outlines: Placeholders don’t output any code until they’re used with @extend.

// % denotes an extended block %item {   display: block;   width: 50%;   margin: 0 auto; }  .MyComponent {   @extend %item;   color: blue; }  // Compiles to .MyComponent {   display: block;   width: 50%;   margin: 0 auto; }  .MyComponent {   color: blue; }

OK, wait. So it output .MyComponent twice? Why didn’t it simply combine the selectors?

These are the questions I had when I first started using placeholders (and then subsequently stopped). The clue is the name itself. Placeholders simply hold a reference to the place in the stylesheet they were declared. While a mixin copies the properties to the location it is used, placeholders copy the selector to the place where the placeholder was defined. As a result, it copies the .MyComponent selector and places it where %item is declared. Consider the following example:

%flexy {   display: flex; }  .A {   color: blue; }  .B {   @extend: %flexy;   color: green; }  .C {   @extend: %flexy;   color: red; }  // Compiles to .B, .C {   display: flex; }  .A {   color: blue; }  .B {   color: green; }  .C {   color: red; }

Even though B and C are declared further down in the stylesheet, the placeholder places the extended properties tall the way up to where it was originally declared. That’s not a big deal in this example because it’s really close to the source where it’s used. However, if we’re adhering to something like the 7-1 Pattern we covered earlier, then placeholders would be defined in a partial in the abstracts folder, which is one of the first imported files. That puts a lot of style between where the extend is intended and where it’s actually used. That can be hard to maintain as well as hard to debug.

Sass Guidelines (of course) does a great job covering placeholders and extend and I would recommend reading it. It not only explains the extend feature, but at the end, advocates against using it:

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, […]


There are many other features of Sass I didn’t cover here, like loops and lists, but I’ve honestly haven’t relied on those features as much as the ones we did cover in this article. Take a look through the Sass documentation, if for nothing else, to see what things do. You may not find a use for everything right away, but a situation may come up and having that knowledge in your back pocket is priceless.

Let me know if I missed something or got something wrong! I’m always open to new ideas and would love to discuss it with you!

Further Reading

The post Sass Techniques from the Trenches appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]