Constructing flexible patterns with Sass

Jay Kariesch
Level Up Coding
Published in
4 min readNov 16, 2019

--

Working on a team can be great, though often I find myself working with folks who are less than enthused about styling. This isn’t a bad thing — it just means that as a developer I have to have a softer approach to counter potential frustrations, and be much more explicit with whats, hows, and whys. You know what it’s like — that moment your heart hits the floor when someone opens a PR and — gasps — they’ve modified your styles. Not just any styles, but very intentional styles. Styles that you thoughtfully put together while thinking 10 steps ahead at where it fits in the overall pattern scheme. How can we curb this, and can we curb it? How can we create a flexible patterns?

This is a common elephant in the room:

“Is our pattern library flexible enough to serve its consumers?”

If you’re working on an enterprise library, the answer is usually “no”. Even with brand guidelines, designers will always pump out new designs with slight variations, and changing the pattern itself then putting it out in a release can lead to unexpected results for its consumers.

While I don’t have a silver bullet, I do have a solution that provides a bit of flexibility that might help you avert a “recreating the wheel” crisis, allowing you to avoid unexpected breaks in development velocity.

Alright, so I’m babbling. Let’s get on with it already.

I present to you…

Constructor-based Mixins

I know, the name is less than spectacular, but who knows — maybe we can change that!

Simply put, constructor-based mixins mirror Javascript constructors in that you can pass a map into a mixin, using the mixin as a constructor for an entire pattern, and it merges (non-destructively) the new values with defaults. So…basically, it’s just a regular mixin with a jargon-y name, but it has an internal pattern — so jargon.

In Sass, the pattern is as follows:

  • Create a mixin that accepts a map .
  • Add a default value of an empty map to the mixin args.
  • Use a non-destructive map merge method to merge new properties with a default map.
  • Assign the map properties to your Sass rules.

Here’s an example that uses this method:

The variables $card-config and $button-config contain maps of all the properties their corresponding mixins expose for modification. If you decide not to modify a property, that’s not a problem, since a non-destructive merge method is being used. It’ll just fallback to the defaults.

You can see the defaults by commenting out the maps, or just prevent them from being passed into the mixins.

Let’s take a look at what’s going on behind the scenes:

@mixin card-constructor($map: ()) {

$config: map-extend(
(
background: #81866e,
text: white,
padding: 18px 12px,
avatar: (
size: 100px,
background: #F7882F,
radius: 50%,
accent-1: #DCC7AA,
accent-2: #F7C331
)
),
$map
);
...}

The mixin is created, and as previously mentioned, an empty map is assigned to $map as its default value. A map-extend Sass function is being used to merge the defaults with $map.

map-extend

map-extend non-destructively merges two maps. You can see the code in the SCSS tab in the codepen above, or view the source here (credit to Sebastian Nitu), as it’s a third party function that’s not built into Sass.

The function signature for map-extend looks like this:

@param $map (AKA $merge-into in the example)
@type first
@param $maps (AKA $merge-from in the example)
@type list of maps
@param $deep (AKA $is-deep-merge in the example)
@desc Whether or not to enable recursive mode.
@type boolean
@default false
@return merged map
map-extend($merge-into, $merge-from, $is-deep-merge)

You can see that the mixin also contains the card pattern’s styles. Let’s examine a few rules.

.card {
background: map-get($config, background);
border-radius: 4px;
color: map-get($config, text);
padding: map-get($config, padding);
min-height: 400px;
width: 300px;
&__avatar {
background: map-fetch($config, avatar, background);
border-radius: map-fetch($config, avatar, radius);
box-shadow: 12px 12px 0px 0px map-fetch($config, avatar, accent-1), -12px -12px 0px 0px map-fetch($config, avatar, accent-2);
margin: 0 auto 36px auto;
padding: 24px;
width: map-fetch($config, avatar, size);
}
...}

map-get + map-fetch

map-get retrieves a shallow value from the $config map and assigns it to a corresponding rule. map-get is built into the language, so it doesn’t require any third party dependencies. Here’s what the function signature looks like:

map-get($map, $map-key)

You’ll also notice map-fetch. map-fetch is a deep map-get, meaning it can return nested values. It’s an easier way to accomplish this:

$avatar-map: map-get($config, avatar).some-class {
background: map-get($avatar-map, background)
}

As it’s not built into Sass, you’ll need to add the code to your code base. Its function signature looks like this:

map-fetch($map, $keys...)

The ellipses means it accepts an innumerable amount of key arguments, so you can grab values no matter how deep they’re nested.

Using with React, Vue, or Angular

The easiest solution is to publish your Sass mixins to a private npm registry as you would any pattern library. Then install it as a dependency in your project, and import mixins into your component’s Sass file directly from node_modules.

Conclusion

Now that we’re at the end, I hope you’re leaving here with at least one takeaway, whether it’s Sass maps, a funky new way of using mixins, or a solution to a problem. Thanks for reading!

--

--