Don’t Over-@extend Yourself in Sass (Or: There’s a Class for That!)


Sass logo

I recently built the theme for WPShout.com in Sass, the well-loved CSS preprocessor that we’ve written about a few times. As I dug deeper into the project, I noticed a conceptual issue coming up repeatedly that I’d like to share.

Essentially: I’m worried about the power of the @extend selector to move us away from semantic, easy-to-interpret HTML markup and CSS styling rules. In many cases, I’m finding that @extend solves problems that may be more cleanly addressed through simple classing and appropriately marked-up HTML.

First Issue: Too Many Selectors

One of the most common knocks against Sass is that—to quote an article addressing Sass’s critics—”The extend directive creates a lot of ugly selectors.”

As a short illustration, this Sass:

.called-out { 
	font-weight: bold;
	text-shadow: 1px 1px 0 #333;
}

strong {
	@extend .called-out;
}

.nav-menu-item {
	@extend .called-out;
}

p.highlighted {
	@extend .called-out;
	color: #555;
}

Compiles to this CSS:

.called-out, strong, .nav-menu-item, p.highlighted {
	font-weight: bold;
	text-shadow: 1px 1px 0 #333;
}

p.highlighted {
	color: #555;
}

Now why is that a problem? Picture a stylesheet in which 30 elements extend .called-out. Imagine how ugly the CSS would look: that would be 31 selectors all inheriting the same style—or, if you prefer, “a lot of ugly selectors.”

To make this real, let’s look at an actual CSS styling rule from the WordPress plugin WooCommerce:

.product .single_add_to_cart_button, .cart .button, input.checkout-button.alt.button, .shipping-calculator-form .button, .multistep_step .button, #place_order.button, .single-product .single_add_to_cart_button.button.alt, .woocommerce a.button, .woocommerce button.button, .woocommerce input.button, .woocommerce #respond input#submit, .woocommerce #content input.button, .woocommerce-page a.button, .woocommerce-page button.button, .woocommerce-page input.button, .woocommerce-page #respond input#submit, .woocommerce-page #content input.button {
	background-color: #605f5e;
}

This type of unusable, uninterpretable styling rule needlessly complicates working in WordPress, and requires unreasonable amounts of creativity to work around.

How did WooThemes get there? Well, as it turns out, they did it with indiscriminate nesting, another widely diagnosed problem that results from misusing CSS preprocessors. However, this is a very serviceable real-world example of the same Selector Hell that overuse of @extend can land you in, so we’re going to go with it.

Is There a Systematic Problem with @extend?

Is there something about @extend that just makes me uneasy? Is that uneasiness justified?

Over the past two large Sass projects, I found myself using @extend more and more sparingly. At first, this tendency fit comfortably into the usual “everything in moderation” arguments. But then, as I started to imagine someone else reading my CSS stylesheet—and not the SCSS markup it’s compiled from—I found myself starting to prefer generally frowned-upon practices like argumentless @mixin definitions, just to get away from @extend. Is there something about @extend itself, under any circumstances, that just makes me uneasy? Is that uneasiness justified?

What @extend Does

The following sentence (here’s the original source) nicely encapsulates the essential workings of @extend:

“One class can have all the styles of another class, as well as its own specific styles.”

The question is: Is that a good thing? Let’s look at an example of Sass @extends supposedly done right. The full code is here; we’ll just be examining a key piece of CSS:

h1, article .title, footer .big-deal h2 {
  font-size: 2rem;
  line-height: 1.75;
}

As in our own example with .called-out above, what you see in this compiled CSS is: a seemingly random assortment of arbitrarily marked-up CSS selectors inheriting the same properties.

How @extend Can Obfuscate Meaning

The problem is not just that there are too many selectors; it’s also that there’s no good way for a human to keep track of which elements are affected by which styling rules. article .title behaves just like an h1, and so, randomly, does footer .big-deal h2.

Imagine the site being modified by a future coder who is unfamiliar with both Sass and your own thought processes. That coder has access to two things:

  1. Innocent-looking HTML markup like the following:
    <footer>
    	<div class="big-deal">
    		<h2>Footer</h2>
    	</div>
    </footer>
  2. CSS rules that apply seemingly at random across that markup. What selectors can we expect to resemble an h1? An element with class .title will, but not if you try it in the sidebar. A footer h2 will, but only if it’s inside the .big-deal div. (And an arbitrary number of other rules, jammed together into one giant selector.)

In other words:

Improper use of the @extend directive obfuscates the reasoning behind a site’s styling rules.

The use of @extend risks essentially creating CSS classes that nobody but users of Sass can see.

The HTML markup contains no alert that a footer h2 is going to suddenly resemble (or refuse to resemble) an h1; and the big bucket of selectors of varying length and specificity that get h1-like treatment is going to be very difficult for future readers of the CSS to understand, as well.

Phrased differently: The use of @extend above has essentially created a CSS class that nobody but users of Sass can see. Our “h1-like” class shows up nowhere at all in the HTML markup, and it applies haphazardly across existing CSS selectors following a logic that only the Sass-savvy original site designer (who can trace the @extends) can understand.

Nobody understands what’s going on!

Mark Up Your Markup!

Why make styling rules that reference a separately defined class, when you can just give elements the class itself?

Maybe we can solve this problem by getting back to the roots of HTML/CSS: adding classes to HTML elements we wish to style. Why use @extends to make styling rules that reference a separately defined class, when you can just give elements the class itself and avoid risking confusion?

Let’s look at the following solution to the “h1-like” problem explored above:

HTML:

<footer>
	<div class="big-deal">
		<h2 class="largest-title">Footer</h2>
	</div>
</footer>

CSS:

.largest-title {
	font-size: 2rem;
	line-height: 1.75;
}

Isn’t it better to add information to your HTML markup, and make the CSS the simplest possible reflection of that information?

This solution has one rule, one selector, and many potential HTML elements that inherit that rule. Every element with the .largest-title class gets the rule, and no element without it doesn’t.

I like this an awful lot better than the @extend “invisible, nameless, haphazardly-applied class” solution to the same problem. Assuming you have access to the HTML side of the site you’re styling, isn’t it a lot nicer to add information to your HTML markup, and make the CSS the simplest possible reflection of that information?

If the argument is that it’s easier, or otherwise better, to add classes to the parent div element rather than all its child elements (and pretending that the .big-deal class gives other important properties to the div itself or its children), I still prefer this solution:

HTML:

<footer>
	<div class="big-deal contains-largest-title">
		<h2>Footer</h2>
	</div>
</footer>

CSS:

.largest-title, .contains-largest-title h2 {
	font-size: 2rem;
	line-height: 1.75;
}

This is slightly more complicated than the previous solution, but it still avoids the @extend trap of blind inheritance. The styling rule now only applies to things that are of class .largest-title, or are inside elements of class .contains-largest-title (and are h2s). There’s no risk that, for example, article .title gets a style that nothing in the HTML suggests it should get for reasons that are buried in a paragraph-long selection rule.

Similarly, if WooCommerce could add the appropriate classes on the HTML side, wouldn’t the following have been so much nicer?

.wc-button {
	background-color: #605f5e;
}

If you want something to look like a WooCommerce button, you give it the .wc-button class. Then you can go and see in your markup what’s a button. The whole thing works, without any surprises.

What Do You Think?

This post has been an attempt to voice my suspicion of the @extend directive in Sass, and its penchant for creating logics that only Sass users can understand.

What do you think? Is @extend just a weird idea? Are there sensible ways to use it I’ve omitted above? Should they deprecate @extend and send me a stylish Sass jacket for being so thoughtful? (Side note: I need a new jacket.) I’d love to hear from you in the comments below!

12 thoughts on “Don’t Over-@extend Yourself in Sass (Or: There’s a Class for That!)

  1. Pingback: Don't Over-@extend Yourself in Sass | Foisan Blog

  2. Russell Bishop

    Thanks for the interesting read. Personally I’m unsure as to wether you should be writing your SASS for future non-SASS authors, that’s an odd compromise for a progressive set of tools. The problem with bailing on extend and using classes again is that you can’t always add a class to your markup, ala clients using a CMS.

    The woothemes example you use is just an example of bad development, not one of over extending, as each rule is out of clients hands (in templates) and not managed in content.

    Reply
    1. Fred Meyer Post author

      Hey Russell,

      Thanks a lot for the thoughts. I agree that @extends can be useful when you don’t have full control over your markup, although I still worry about the risk of creating obfuscated styling logics as the article describes.

      On whether Sass authors should try to generate readable/editable stylesheets, I think it depends on who it’s reasonable to expect will be studying them in the future. I would say projects like WordPress themes and plugins (such as WooCommerce) should certainly make that a priority, since an unknown number of people—many of whom won’t know Sass/LESS/etc.—will need to understand and/or modify their styles.

      Cheers! Your portfolio looks great, by the way. Loved (guiltily, as we also run WPShout.com) the shot at WordPress in the footer. Let us know if you’re ever looking for development help. 🙂

      Fred

      Reply
  3. Pingback: Why You Should Avoid Sass @extend

  4. Glenn

    Wow! You guys are using WordPress and Sass. Me too. Just started and so far so good, with one almost trivial exception. When I’m migrating the code from localhost to the live site, and generate my compressed style.css for the first time, the (‘live’) site indicates that I have a broken theme. Apparently this is because WP expects a line feed immediately after the expected style.css comment block. The line feed was compressed out. Once I manually add the line feed back in everything is fine 😛

    Reply
    1. Fred Meyer Post author

      That’s good to know; could certainly cause confusion.

      Please stay in touch as you keep mixing WordPress and Sass—it’s fun to compare notes!

      Reply
  5. Pingback: Reference Imports in LESS (are kinda cool) | Tech +

  6. Pingback: Reference Imports in LESS (are kinda cool) | Wind River Graphic Design

  7. Pingback: Reference Imports in LESS (are kinda cool) - Ifan Maulana Official siteIfan Maulana Official site

  8. Pingback: Reference Imports in LESS (are kinda cool) - A Geek's View

  9. Pingback: Reference Imports in LESS (are kinda cool) | ejoni S.A. - Small Business Solutions

  10. Pingback: Reference Imports in LESS (are kinda cool) - Daniele Milana

Leave a Reply

Your email address will not be published. Required fields are marked *