Skip to content

Goodbye Sass?

Posted on by Lee Moody.
Tagged with Sass

TL;DR:There's a bunch of reasons it would be desirable to drop Sass. Though there are some complexities and caveats to think through, I think now could be the time. Starting with Origami. Please send your thoughts and feedback, and keep an eye on future TGG (technical governance group) proposals.

What is Sass

Sass is a stylesheet language that’s compiled to CSS, this is what gives a web based project its style, layout, brand.

I use the term “style authors” throughout this post to refer to engineers, designers, and anyone who might be working with CSS. At the FT this predominantly means engineers.

Why try to drop Sass

Sass is a compiled language. There’s a build step to transpile Sass to CSS, so the client (browser) can understand and render our product.

  • Transpilation slows a CSS author’s feedback loop, reducing productivity.
  • It also makes publishing projects a little slower.
  • Sass adds language features for CSS authors to learn, increasing training and onboarding costs.
  • CSS is introducing native nesting, which works differently to Sass nesting. Therefore, Sass will no longer be a superset of CSS. Meaning a person could write the exact same Sass as CSS and get different results. This is confusing as authors must understand the distinction and shift mental models depending on the project at hand.

Until recently it was easier to argue that Sass’ benefits outweighed its costs. That’s a more difficult argument to make today as CSS natively has a solution for many Sass features.

Variables

Sass variables allowed us to share and reuse values, such as the hex code for FT Pink:

$ft-pink: #fcd0b1;

As the FT has finally, finally, dropped support for Internet Explorer we can use CSS Custom Properties now:

--ft-pink: #fcd0b1;

These are actually way more interesting than Sass variables because, unlike Sass variables, CSS Custom Properties work with the CSS cascade.

Functions

Sass provides a number of functions which return a value and may take arguments.

I’ve struggled to find an example in Origami which couldn’t be replaced by a CSS Custom Property (oSpacingByName) or which couldn’t be handled by other tooling more effectively (oColorsGetContrastRatio).

Sass also provides helpful built in functions such as to manipulate colour. For example, to darken a colour:

.example {
	$paper: #fff1e5;
	background: darken($paper, 10%);
}

We could achieve the same thing using CSS’ hsl function, but would have to publish hue and saturation CSS Custom Properties for all colours in our palette. And it’s not exactly pretty. Still this isn’t a common task:

.example {
	--paper-hue: 27.69deg;
	--paper-saturation: 100%;
	--paper-lightness: 94.9%;
	background: hsl(
		var(--paper-hue),
		var(--paper-saturation),
		calc(var(--paper-lightness) - 10%)
	);
}

Are there any Sass functions we just must have? I can’t think of any.

Mixins

Sass mixins allow us to share chunks of CSS. We can even pass arguments to a mixin to control its output. For example a link mixin which accepts a theme to customise a link’s colour:

@mixin link($base-color: black, $hover-color: teal) {
	color: $base-color;
	text-decoration-line: underline;
	text-decoration-thickness: 3px;
	&:hover {
		color: $hover-color;
	}
}

.link {
	@include link();
}

.link.link--my-fancy-link {
	@include link($base-color: hotpink, $hover-color: mediumvioletred);
}

.link.link--another-lovely-link {
	@include link($base-color: turquoise, $hover-color: cadetblue);
}

There is no CSS equivalent but maybe this isn’t so necessary now. With CSS Custom Properties and the CSS cascade we can achieve the same affect as this example, in fewer lines of code and with less repetition in the final CSS output:

.link {
	--link-color: var(--base-color, black);
	color: var(--link-color);
	text-decoration-line: underline;
	text-decoration-thickness: 3px;
	&:hover {
		--link-color: var(--hover-color, teal);
	}
}

.link.link--my-fancy-link {
	--base-color: hotpink;
	--hover-color: mediumvioletred;
}

.link.link--another-lovely-link {
	--base-color: turquoise;
	--hover-color: cadetblue;
}

A more complex example of an Origami Sass mixin is oButtonsAddTheme. Now this is pretty cool. You can basically say “give me a hotpink button” and it’ll go away and follow pre-determined FT design rules to generate every state for you (hover, focus, pressed, disabled, etc.) and check for accessibility at the same time. Writing this in Sass was wildly complex though. It’s really quite hard to reason about or modify. It wouldn’t scale to support more brands with their own rules. And, it’s not used all that much – we have a limited set of buttons for a reason! Sass is the wrong tool for that job.

Nesting

Sass allows nesting of selectors. For example this Sass:

.example {
	.example__content {
		h1 {
			// declarations
		}

		a {
			// declarations
		}
	}
}

Becomes this CSS:

.example .example__content h1 {
	// declarations
}
.example .example__content a {
	// declarations
}

It might not look like much from this simple example, but nesting makes authoring CSS so much more efficient and, by grouping selectors, easier to read too.

There is now a CSS Nesting Module specification (W3C Working Draft) to bring native nesting to CSS. This has been shipped in major browsers including Chrome, Edge, Safari, and (very soon, behind a flag) to Firefox.

Woo! But, the awkward news is that this works differently to Sass’ implementation. Under the hood, CSS nesting uses the :is pseudo-class so that:

main,
#intro {
	& div {
		...;
	}
}

Is interpreted by the client (browser) as:

:is(main, #intro) div {
	// declarations
}

In Sass, the same code would transpile to the following CSS:

main div,
#intro div {
	// declarations
}

Since :is takes the specificity of the highest selector, these two interpretations (Sass vs. native CSS nesting) have different specificities, and could therefore apply differently. I’ll leave Kilian’s blog post, the gotchas of CSS Nesting (where I stole this example from), to demonstrate this in more detail.

My take: Yikes! Sass have committed to aligning with native CSS nesting by switching to outputting :is, but not until it’s supported by 98% of the global browser market share. The new CSS native syntax will be actively taught and used long before then, causing confusion for those new to Sass or switching between Sass/CSS projects in the meantime.

Also important to note is that the & symbol in Sass supports the parent selector to append additional text:

.accordion {
	&__copy {
		// declarations
	}

	&--open {
		// declarations
	}
}

Which transpiles to:

.accordion__copy {
	// declarations
}

.accordion--open {
	// declarations
}

This isn’t supported by the native CSS & syntax. The Sass team do not plan to align what & means in Sass and CSS because “this behavior [sic] is too important to existing Sass users”. It shouldn’t be, the &-suffix just makes debugging hard, because it’s difficult to locate where a selector has been defined in a large project. Type a few extra characters, don’t use the &-suffix syntax in Sass.

Now Sass is no longer a CSS superset, it’s a compelling time to switch to the upcoming native CSS syntax. To support older browsers we can use PostCSS with postcss-nesting and postcss-is-pseudo-class, though there are some manageable caveats there too.

Import

Sass also helps us organise our CSS into manageable files using Sass’ @import, which is being replaced by @use.

Using Sass we can write two Sass files to separate our button and link styles, then combine them into a single file later:

@use 'buttons';
@use 'links';

CSS can do this too with the native @import.

@import 'buttons.css';
@import 'links.css';

CSS’ @import isn’t performant, as the browser needs to download one CSS file before it can discover and download any subsequent imports it contains. This creates a waterfall of requests rather than downloading them at the same time, in parallel. We’ve previously found site performance has a direct impact on engagement, so this is something we should avoid.

Again we can overcome this with PostCSS, by using postcss-import to resolve @import for us before publishing our CSS.

Performance

If we need to rely on PostCSS with multiple plugins to support nesting and support for older browsers, will we see any performance gain? Yep! Seems so.

I created 2 test projects to compare performance, using a real world complex component. For the Sass version I included Origami’s o-buttons Sass, whilst for the native CSS test I copied its compiled CSS in full. To both I added nesting examples as above. This ensures both tests transpile a large amount of CSS (o-buttons CSS output is way bigger than it needs to be) and also shows a real life example in terms of Sass complexity. Both projects used Vite to build, an average of 5:

  • The Sass version: 3.78 seconds.
  • The CSS + PostCSS version: 0.25 seconds.

This is a little unfair to Sass. o-buttons includes some really wild Sass and PostCSS had relatively few nest transformations to make. However, as this uses a real life component, it’s indicative of the code Sass enables and its potential performance impact.

Style authors are always making changes quickly, iteratively, and checking back to see the results of their changes. It’s easy to make hundreds of changes through the course of a day. This time adds up. Imagine working on a feature and checking in on your progress every 5 minutes (conservative). Across 30 people that’s 12 hours wasted each week, just waiting for a project to build.

Also, it’s very easy to get distracted whilst waiting… doom scrolls.

We can actually boost this further because a CSS author won’t need to run the PostCSS plugins at all to preview their work in a modern browser.

Choice

Origami components currently force teams to use Sass, unless they’re using the Origami Build Service which takes away control and customisation regarding a product’s build.

Other than Sass, Origami is based on web standards. Teams can choose the best language or framework for their team and project, Origami is flexible. We see this manifest with external parties using Vue.JS to build ft.com pages; the apps team using Preact; and others using vanilla JS too. Sass is an exception for the team. It would be great to give teams more control regarding how they author their styles with Origami – whether that’s sticking with Sass or not.

Though we should work on consistency of approach across teams for a bunch of reasons, Origami shouldn’t get in the way of trailing new approaches. Favouring web standards helps.

Documentation

There’re a bunch of tools for documenting Sass, notably for Origami:

Moving away from Sass poses some new challenges for documentation and code completion. However I think we can drop Sass and still get to a better place, where a designer can hand off to an engineer much more effectively, using the same language and Origami components or variables.

Whether we continue to use Sass or not, it’s very likely we’ll adopt Design Tokens to manage support for multiple brands and themes. Ben already wrote about Origami’s most recent design token trials in Origami Team Trials Design Tokens (Confluence, behind a login). These tokens represent design decisions such as colour, typographic style, spacing, etc. We may store meta data against them including documentation. We will then be able to surface these in Figma, READMEs, and elsewhere – awesome! It will reduce a lot of friction between designer and engineer during hand over and provide better documentation for both.

Moving away from Sass’ @warn feature doesn’t seem so bad, either. Sass logs get mixed up with warnings from a bunch of complex tooling and is easy to ignore. We’ve seen in recent years more teams building multiple entry points (multiple separate Sass files) which duplicates warnings. They become annoying, hard to parse, and even easier to ignore. Using Design Token meta data again we can document deprecation warnings for everyone to see across design and engineering tooling.

I expect code editor tooling and autocompletion to be on par. VS Code’s SCSS IntelliSense Plugin is super helpful for local development, but less helpful at working with dependencies. I couldn’t get it to make suggestions for Origami Sass installed via npm, but may have got there by fiddling with settings.

Do you think we have any documentation or tooling around Sass that is irreplaceable?

Support

We already have teams at the FT who don’t use Sass. Sass will eventually release a major change to better align with native CSS nesting behaviour. Whether we stick with Sass now or not, we will end up in the position of some teams/projects using the current Sass nesting behaviour and some not. Origami is a good, collective project to guide us through this transition.

If we do choose to drop Sass, Origami would publish plain CSS already transpiled by PostCSS and ready to ship. Therefore style authors using Origami components wouldn’t need to worry about what tools were used to create them. Moving from Sass and when, if at all, would be up to individual teams. Meanwhile, the Origami team would be able to offer dedicated support for contributors and provide training for others who are interested in moving away from Sass.

(Oh my, there’s so many cool things happening in the world of CSS that I want to discuss and share and learn about with you fellow CSS authors).

Conclusion

I started writing about how I’d love to switch away from Sass within the FT but how we just can’t yet. Through writing this and exploring the limitations of PostCSS plugins some more, I’ve come to think we can and should. Starting with Origami.

What do you think?