The use of Sass on compiled themes is being deprecated, and stylesheets should migrate to native CSS features. For more information on this change and how to use Sass locally in your theme development workflow, please read our announcement blog post on deprecating Sass.
Design systems and the modern spec play a huge part in how to approach CSS problem solving today. In part one of this series, we looked at how modern CSS tools can help us write more organized and programmatic CSS. Part two builds on those tools by looking at how we can use them to create resources for our websites. We will use Sass to auto-generate a CSS system, complete with namespacing. Then we’ll dive into navigating the CSS spec and touch on examples of how the current state of the spec helps solve common problems.
Systems
It seems like everywhere you turn there is talk about using design systems. There are whole conferences about design systems, surveys reporting design system use and statistics, and Slack teams devoted to the concept. When looking at design systems from the outside, the high-level view may seem simple, but as you get closer, it becomes more and more complex. The major thing to understand is that a design system is a term for a collection of independent systems that work together to give designers and developers a way to quickly create consistent, efficient designs and code.
CSS systems are often at the heart of a good design system. Typography, spacing, layout, grid, and color are commonplace elements of a design system, but they can be used independently of a full design system. Tools like Sass and PostCSS can help automate the creation of these systems that are used in the HTML. Organizational methods like BEMIT provide a framework to structure these systems’ class names. As an example I’ll step through using Sass to automate the creation of a simple spacing system that I have used many times over the last several years. The great thing about this system is that it’s very malleable and can be adjusted to fit the needs of each particular project.
You might also like: A Modern Approach to CSS Pt. 1: Tools and Organization.
Step 1: Variables and maps
When starting to create this system, it’s helpful to lay out some guidelines, essentially some docs-driven development. The goal of this system is to provide classes that can be used anywhere in our HTML to add padding or margin values to elements. These classes need to provide a quick option to add padding
or margin
to the top, right, bottom, left positions individually as well as have classes that allow for adding an equal spacing to all positions, the horizontal positions, and the vertical positions. This system ought to be auto-generated to allow a consistent output with minimal code written.
This will be a Sass file that follows the ITCSS method to organize the file, so we will name this file _trumps.utilities.spacing.scss
. The naming here is how I have found organization helpful. I start the name off with the ITCSS section the CSS will fit into, then I provide a descriptive category, in this case utilities. Since it’s quite likely I will have multiple utility systems, I’ve added a further description. Being descriptive like this with your file names will help you and others find the appropriate files quickly as the codebase grows.
In this new Sass partial file we will begin by adding a variable called $spacing-limit
and set the value to 4
.
This variable is the maximum value for our spacing system. The system we write will iterate to create classes for spacing values from 0
to 4rem
in 1rem
increments.
Next, we create a map of the positions we want to target. As mentioned before, we’ll want to target the top, right, bottom, and left positions, and provide a method for creating classes that target all positions equally, the combined top and bottom values as a vertical class, the combined right and left values as a horizontal class. As mentioned in the namespacing section, it’s important to keep naming concise yet human-readable. With namespacing in mind, we will create a key-value pair where the key is our concise name, limited to three characters, and the value will be the full name for the property. Note that since all, vertical, and horizontal, are collections of positions, their value is null
.
Step 2: Incremental looping with the class names
Like a Tarantino movie, we’re going to put the ending of this demo in the middle. The reason for this is to understand what information we’re going to be passing into our loop mixin in the next step. For starters we’ll create an @for
loop that will increment from 0
to our $spacing-limit
value of 4
. This increments in whole numbers, so our classes will consist of 0, 1, 2, 3, and 4 as values in our spacing classes.
Next we’re going to create a new variable that takes the $space
incremental value and adds the unit to it, in this case a rem
. With Sass it’s as easy as adding the two together.
Now we need to create our class names, or the start of them at least. Since these are utility classes, we’ll namespace them as util-
, then for the padding
classes we’ll use pad
, and margin
we’ll leave as is.
Our next step will take care of creating the mixin we’ll need to add in to these two selector blocks, but for now we can call the mixin we’re about to create. This mixin will be named ‘spacing-loop’, which is initiated by the @include
, which includes a mixin in the selector block. Note at this point your Sass will error when generating because it will not have a mixin called spacing-loop
. There are three values we want to pass through to our yet to be mixin, that is the name of the property we are targeting as a string, so "padding" for the .util-pad
class and "margin" for the .util-margin
class. Then we want to pass through the $space
value, as well as our unit version $value
.
Step 3: The positions loop mixin
Our final step is the most complex step and may require some additional reading to fully understand. For starters, let’s make the mixin and define the properties it will accept. We’ll try to keep the names the same to prevent confusion, $property
is the property name we’re targeting, either margin
or padding
. Then we’ll pass through the $space
and $value
properties from the previous step’s variables.
Now that our Sass compiles again, we will create an @each
loop that goes through our $spacing-key
map from the first step. The structure here tells the @each
to get the key and the value from the map and assign them to the variables $poskey
and $posvalue
, respectively. The pos
in the names refers to the position.
Next we’re going to programmatically create our class names by using a mixture of variables. The first variable is represented by an ampersand (&
). This is a special feature of Sass called the Parent Selector, and it captures the scope of the selector up to the point it’s called. In this case it will translate to .util-margin
and .util-pad
, depending on which selector block the mixin is running in.
The next two portions are standard Sass variables, but since they are being used in a class name, they must first be interpolated so they output safely as Sass, otherwise Sass will think the class name is util-margin-$poskey-$space
. As the @each
loop runs through all the entries of the map, it will insert the $poskey
value and the current $space
iteration that has been passed down from the @for
loop from Step 2. In the case of a utility class that targets all margin positions with a spacing of 2
, the class created from this setup will read .util-margin-all-2
. Note that when a Sass variable is used as a selector, it must be interpolated by wrapping the variable in a curly bracket set that begins with a hash/pound sign, for example #{$variable}
.
Now that our class selectors are created, it is time to add in the last bit of information, the selector block properties. Since our keys for all
, vert
, and horiz
are null
, we need to perform and @if
statement to detect those cases and output the correct property or properties. The first check will see if $poskey
is equal to "all", in which case it will grab the $prop
value of either "margin" or "padding" and output the $value
.
Note that when using a variable as a property name, it must also be interpolated. Anytime a variable is outside of being called as the value as a property, it must be interpolated. We can repeat this check for vert
and horiz
and create the appropriate properties. Once we have gone through all our if conditions, we can wrap up with an @else
condition that will grab the $posvalue
, i.e. key value, for the appropriate iteration, in the case of $poskey
equaling "right" the $posvalue
will output right
.
Pulling this all together is roughly 50 lines of Sass that will output 70 unique spacing utility classes. These classes can then be put to work rapidly spacing out content on your site by adding the appropriate class for the amount of spacing needed. The classes created by this system have tremendous browser support as it uses long-standing properties and approaches. As older browsers become less necessary to support, we can start expanding on this system with the use of CSS Custom Properties, as Kasey Bonifacio shows in her article Manageable Utility Systems with CSS Variables.
You might also like: Reactive UI Animations with CSS Variables.
The CSS spec
When it comes to writing modern CSS, we have to come back to the CSS spec itself. The CSS Working Group has taken many cues from the last decade of web development trends and practices to heart. Over the last few years CSS has gained feature queries, custom variables, math capabilities, and unique ways to identify elements. And lest we forget all the layout options we have with Flexbox and CSS Grid in the mix.
With all these new features it can be difficult to know where to start. My day-to-day work involves referencing the MDN Web Docs and Can I Use websites regularly. There are other resources available, but these two have become my development companions. It isn’t necessary to know every property, but knowing how to navigate which property to use at which circumstance is an important skill. Simply exploring these two websites can reveal a lot about the options available to you as a front end developer.
I want to leave you with three things specific to CSS that I have really come to rely on. These things are—unlike all the fancy tooling and methodologies that have been discussed so far—pure CSS.
Progressive enhancement and feature queries
One brilliant feature of CSS is the way that properties become more important the further down the list they are. This means that when CSS properties are well-ordered, they can provide specific styles for specific browsers without the need to rely on JavaScript-based feature detectors such as Modernizr. For example, when writing layout styles, start off with floats, then add in Flexbox, and only then add in CSS Grid. This will allow browsers like Internet Explorer 9 to get the float-based layout.
Here is a quick example. Let’s start with the older layout method of floats, and what we’ll need to accomplish it. In this example, I’ll be using display: inline-block
as the method to clear the floated elements.
With IE 10 and 11 along with older versions of modern browsers, we can add in the Flexbox version. But, since we want this to be progressive and flow with the cascade, we append like properties below the version for older properties:
It’s pretty incredible that all we need to add is display: flex;
and these classes are now using Flexbox. The properties around Flexbox negate the floats in the children classes, but the widths are still used. This works because the natural state of a Flexbox is to keep all the children elements on a single line, and our widths on the children allow the space to be filled fully, without concern of the second child wrapping below the first.
The last step in the enhancing example is to add CSS Grid. Although the parent class properties are as simple as adding display: grid
and the grid-columns-template
property, a problem arises with the widths of the children. Unlike the float and Flexbox approaches, CSS Grid widths are determined by the parent via the grid-columns-template
. The widths of the children will now be a percentage of the column width. To remove these widths, we can add in a feature query to check if CSS Grid is supported with @supports (display: grid)
.
As you can see, @supports
works just like @media
, and a feature query can be nested within a media query as well. One thing to note when using this approach is that if you are using Autoprefixer to automatically create vendor prefixes, it will create a vendor prefix of display: grid
for IE 11 as display: -ms-grid
. While IE11 has a very early implementation for CSS Grid, it does not have any support for feature queries. This can cause issues as grid properties are added to the element, but the widths can’t be controlled. In this case, it’s better to group the display: grid
declaration within the feature query as well, and force IE 11 to use the Flexbox approach.
You might also like: What is Progressive Enhancement and Why Should You Care?
Consider using alternative selectors
When we talk about CSS naming methodologies and organization, it’s almost always regarding the use of class selectors. It’s understandable why, since class selectors are a defined selector type that can have multiple values, an aspect that is heavily relied on. However, there are many other ways to target an element that provide a level of conditional-like logic to CSS selectors. Two in particular, which I have found work powerfully together, are attribute selectors and the :not()
selector.
Attribute selectors have been around since CSS 2.1 with support back to IE 7. They are useful in tracking down and styling input
elements by type
, such as a input[type="radio"]
to select radio buttons. They can also be helpful in tracking down a particular value in a space-separate lis in a data attribute, with something like [data-date~="tuesday"]
. Recently, I’ve seen an excellent use of attribute selectors with the aria-expanded
attribute to check if an element is expanded or not and provide the necessary CSS to hide or show the element.
The :not()
pseudo-class is a CSS3 addition that allows a selector scenario to be excluded from applying styles. This comes in handy as a way to apply styles when a particular state-indicating class is not present. For example, if we want to have specific styles for a button when it doesn’t have an icon present, we can write a selector like this: .button:not('.has-icon') {...}
. At first it may not seem like a worthwhile approach to use :not()
, but I have found once it’s considered an option, the problem it solves reveals itself.
An approach I have commonly taken over the last year or so is to combine these two selectors to create styles for generic elements. When writing generic element selectors, such as p
or a
or h4
, those selectors will affect the outcome of every instance of those elements. Element selectors are generally frowned upon because of this cause, and any class that is applied to one of those elements in the DOM will require extra CSS to counteract the styles of the element.
However, it’s terribly convenient to have default styles for elements that might show up without a class. This has led me to include element selectors that do not have a class by adding :not([class])
to those selectors. This allows generic elements to get custom styling but not to the detriment of class selectors. A p {...}
selector becomes p:not([class]) {...}
and now won’t come in conflict with the styles specific to this class used in the DOM: <p class="hero-subtitle">...</p>
.
You might also like: Using CSS Animations to Guide a Better Ecommerce Experience.
Play with CSS
The most important thing that I have learned in my years as a CSS developer is that knowing CSS requires play time with CSS. This idea became quickly apparent when I started using CodePen in 2012. I started by making an animation of an oscillating fan, as a way to try nested element animations. From there I have found the best way to play with CSS is to take a challenge and add restrictions.
"The most important thing that I have learned in my years as a CSS developer is that knowing CSS requires play time with CSS."
Go through and try one of the CSS daily challenges out there, but add your own stipulation to the challenge. Try a new property, force yourself to use only one HTML element to complete the task, or see if you can complete a kata, like FizzBuzz, with CSS alone. When you play with CSS, you learn more than if your usage is limited to only the needs of a project.
CSS as a bridge
CSS is a bridge between design and development, resulting in a language that is unique, capable, and sometimes underestimated. The rich and complex abilities allow for experienced developers to create truly fascinating styles. At the same time the language is still simple and approachable as an introduction to web technologies. These characteristics of CSS require a particular mindset when writing CSS that is neither design nor programming, but somewhere in the middle. The technology has experienced robust expansion over the last several years and there is no sign of slowing.
Technologies such as Sass and Node bring programming and automation to the language, and provide concepts programmers understand that can cause confusion. In return, the CSS spec has been expanded to incorporate ideas introduced by CSS tools, adding feature detection, math, and variables. Despite these further enhancements of adding a programming layer to CSS, the language still requires a specific mindset that keeps the cascade functionality central to developing efficient and practical styles.
Read more
- How to Get Published in the Shopify Theme Store
- Deconstructing the Monolith: Designing Software that Maximizes Developer Productivity
- Free Webinar] Getting Started with Sass and Shopify
- Reactive UI Animations with CSS Variables
- 8 Free Resources For Learning Web Design and How to Code
- How Open Graph Tags can Optimize Social Media Experiences
- 30 Developer Resources to Diversify Your Skill Set
- Basic Elements of Ecommerce Website Design
- How to Build Fast and Secure Websites With the JAMstack