SASS Modules

This article explains SASS modules.

We explain how to modularize SASS using @use and @forward, and how to reuse variables, mixins, functions, theme settings, and public APIs.

YouTube Video

SASS Modules

SASS’s module system helps clearly scope styles and provides a design that is easy to reuse and maintain.

Basic Concepts of Modules

SASS modules solve issues of global namespace pollution and unclear dependencies that were present with the old @import. Each file is loaded as a module with its own namespace, and only the items explicitly exported are accessible from other files.

Example File Structure

First, here is an example of a realistic project structure. Below is an example of a small design system.

 1styles/
 2├─ _variables.scss
 3├─ _mixins.scss
 4├─ _functions.scss
 5├─ components/
 6│  ├─ _button.scss
 7│  └─ _card.scss
 8├─ utilities/
 9│  └─ _helpers.scss
10└─ main.scss
  • This structure makes modularization easy as well as testing and replacing parts.
  • Partial files have an underscore at the beginning of the filename.

Basics of @use

@use loads a module and provides a namespace. This prevents name collisions and clarifies the source module.

1// _variables.scss
2$primary-color: #0a74da;
3$padding-base: 12px;
  • This file is a simple one that only defines variables. Variables published from a module can be referenced directly using @use.
1// main.scss
2@use 'variables';
3
4.example {
5  color: variables.$primary-color;
6  padding: variables.$padding-base;
7}
  • Here, the variables module is referenced with the variables. namespace. This approach makes the origin of each item clear.

Shortening or Aliasing Namespaces

Using as allows you to shorten the namespace.

1@use 'variables' as vars;
2
3.btn {
4  color: vars.$primary-color;
5}
  • This is an example of importing variables using the shorter alias vars. Choose names based on whether you prioritize readability or ease of typing.

Mixin Definition

Mixins and functions can also be defined and used within modules. Keeping comments consistent with the project’s conventions helps with clarity.

1// _mixins.scss
2// Create a simple responsive container mixin
3@mixin container($max-width: 1200px) {
4  width: 100%;
5  margin-left: auto;
6  margin-right: auto;
7  max-width: $max-width;
8}
  • This file defines a mixin for containers. It takes arguments and provides default values.
1// main.scss
2@use 'mixins' as m;
3
4.wrapper {
5  @include m.container(1000px);
6}
  • This is an example of using a mixin with @include.

Defining Functions

Functions are used to return values, and can be defined and reused within modules. By encapsulating calculations for design values in functions, styles become more stable and easier to maintain.

1// _functions.scss
2@use 'sass:math';
3
4// Create a simple px-to-rem converter function
5@function to-rem($px, $base: 16) {
6  @return math.div($px, $base) * 1rem;
7}
  • This file defines a function that converts pixel values to rem. A default base value is also specified.
1// main.scss
2@use 'functions' as f;
3
4.title {
5  font-size: f.to-rem(24);
6}
  • Here is an example of calling a function via @use and applying the result to a style.

Module Export and @forward (API Design)

When you want to expose multiple internal files externally, you can use @forward to create a 'public API'.

1// _index.scss (module entry)
2@forward 'variables';
3@forward 'mixins';
4@forward 'functions';
  • Multiple internal files can be grouped as a single entry point, which then provides the public API. This allows consumers to access all needed features by importing just one entry point.
1// main.scss
2@use 'index' as ds; // ds = design system
3
4.button {
5  color: ds.$primary-color;
6  @include ds.container();
7}
  • The contents of variables and mixins are accessed collectively through the index. @forward makes index the public layer.

Controlling the API with @forward's show / hide options

If you want to expose only specific variables, use the show or hide option.

1// _variables.scss
2$internal-thing: 10px !default; // for internal use
3$primary-color: #0a74da !default;
4$secondary-color: #f5f5f5 !default;
  • Adding !default allows you to publish a value that can be overwritten.
1// _index.scss
2@forward 'variables' show $primary-color, $secondary-color;
3@forward 'mixins';
  • By using show with @forward, you can limit the exposed API to only the necessary elements. Variables and functions used internally will not be visible from outside.

Making Modules Configurable (with with)

If you use !default in the module, values can be overridden using with on the import side.

1// _theme.scss
2$brand-color: #ff6600 !default;
3$radius: 4px !default;
4
5@mixin button-style() {
6  background-color: $brand-color;
7  border-radius: $radius;
8}
  • A module with default values defined by !default can accept configurations via with.
1// main.scss
2@use 'theme' with (
3  $brand-color: #2288ff,
4  $radius: 8px
5);
6
7.my-btn {
8  @include theme.button-style();
9}
  • with in @use allows overriding default variables in a module at import time. This is useful for theme switching.
  • Note that with only takes effect at import time and you cannot change those values later.

Practical Example: Button Component (Complete Example)

Let’s try designing button styles using modules. First, separate variables and mixins into their own modules.

1// _variables.scss
2$btn-padding-y: 8px !default;
3$btn-padding-x: 16px !default;
4$btn-font-size: 14px !default;
5$btn-primary-bg: #0a74da !default;
6$btn-primary-color: #fff !default;
  • Default variables for buttons are defined here. Using !default allows consumers to override these values.
 1// _mixins.scss
 2@use "variables" as v;
 3
 4@mixin btn-base() {
 5  display: inline-flex;
 6  align-items: center;
 7  justify-content: center;
 8  padding: v.$btn-padding-y v.$btn-padding-x;
 9  font-size: v.$btn-font-size;
10  border: none;
11  cursor: pointer;
12}
  • The base mixin for buttons is defined here. They are separated for easy reuse.
 1// _button.scss
 2@use 'variables' as v;
 3@use 'mixins' as m;
 4
 5.button {
 6  @include m.btn-base();
 7  background: v.$btn-primary-bg;
 8  color: v.$btn-primary-color;
 9  border-radius: 4px;
10  transition: opacity 0.15s ease;
11  &:hover { opacity: 0.9; }
12}
13
14.button--large {
15  padding: calc(v.$btn-padding-y * 1.5) calc(v.$btn-padding-x * 1.5);
16  font-size: v.$btn-font-size * 1.25;
17}
  • Button styles are created using namespace references with @use. A variant is defined as .button--large.
1// main.scss
2@use 'button'; // or @use 'index' that forwards button, variables, mixins
  • By importing the button module, you can use the button styles immediately.

Theme Switching (Using Multiple Theme Files)

Theme switching can be done by configuration via with or by creating separate modules and changing which one is used with @use.

1// themes/_light.scss
2$brand-color: #0a74da !default;
1// themes/_dark.scss
2$brand-color: #111827 !default;
  • Define brand colors and other settings in multiple theme files and switch them during build or import.
1// main.scss (light theme)
2@use 'theme' with ($brand-color: #0a74da);
3@use 'button';
  • Choose the theme by either using with or by importing, for example, @use 'themes/light' during the build process.

Private and Public (Prefix _ and !default)

In SASS, adding an underscore to the beginning of a file name marks it as a partial. However, when managing module export visibility, use show and hide with @forward to control what is exported.

You can use @forward to organize the public API and hide internal implementations from outside.

Best Practices for Real-World Use

Below are some basic concepts that are useful when using SASS in practical situations. All of these guidelines will help reduce confusion during development and keep your code organized.

  • If variables are likely to change as part of a theme, add !default. This makes it easier for users to override values.
  • Modules should be divided by responsibility and focused on a single purpose. It becomes easier to manage by separating variables, mixins, and components.
  • Manage public contents with @forward and use show or hide as necessary. Defining the scope of what is public leads to safer design.
  • Use namespaces to make it clear which module each function belongs to. This prevents confusion with other code.
  • Remember that with only works at the time of @use. Since you can't change them later, set configurations at import time.
  • Use an underscore at the beginning of filenames to make them partials, so they are not compiled individually. This makes it easier to combine files into a larger structure.
  • Including usage examples in the index module helps with testing and documentation. It becomes easier for users to understand the behavior.

Keeping these points in mind will help you manage large projects and maintain readable code as a team.

Summary

The SASS module system organizes style code through namespaces, public APIs, and simplified configuration. Skillfully using @use and @forward makes team development, theme switching, and library design much easier.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video