My perspective on a better Magento 2 frontend

With the Magento Imagine conference came a lot of talk about the Magento 2 frontend. While I wasn’t there, I witnessed it via Twitter and was excited to see what was happening. The reason it has my attention is because I am a front-end developer and work with Magento every day — and have for years. Magento is a large, flexible, and extensible platform that must fit the needs of many people. The ability to meet everyone’s goals is no small feat. However, I believe that a well built front-end will better facilitate that.

For this, I want to focus on the CSS and HTML aspect of Magento 2’s front end. I have worked quite a bit with the UI Component / Javascript side and, while there are many areas of potential improvement there, I actually like much of it. Conversely, when I started working with the CSS, I was disappointed, and that has continued despite getting a more full understanding of Magento 2’s stylesheets. I believe that there is tremendous potential for the CSS and hope to participate in improving it. With that, my goal is to reflect on what I, a Magento frontend developer, consider isn’t right with the current stylesheets as well as my perspective on what can and should be improved with the next generation.

First, I want to talk about specific things that I like about the Magento 2 frontend as it is today in v2.1. After that, I will lay out specific shortcomings and my proposed improvements to that. Also, below I am referring to everything as CSS, but with the assumption that it would be a preprocessor. For the type of preprocessor, I hope that SCSS will be used in the future instead of LESS, but at the end of the day, it doesn’t make a tremendous difference.

The good things about Magento 2’s CSS

The _module partial inside of a module’s directory is brilliant. That module’s partials can go into a module directory on the same level. Ideally, the _module partial would simply @import a number of other partials from the module directory. But this structure is fundamentally great.

The ability to override source stylesheets is incredibly natural and works very well. This is a fantastic iteration to the previous version of Magento.

Areas for improvement and specific suggestions:

Variables

One can’t open a Magento 2 stylesheet partial without seeing at least a few variables. In my opinion, preprocessor variables are way overused. Since so many values have been abstracted into variables, finding the actual location to change a particular value takes longer. In addition, it increases the chances of introducing collateral damage because that variable may have been used elsewhere. This leaves a developer in a predicament: remove the variable’s usage in the property, or risk changing it.

Variables can also be overridden based on source order, which means, at least in SCSS, that there may well be multiple locations where the variable is declared. Sometimes those variables even have other variables from different files as their values. The last declaration of the variable still must be found in order to update it. This creates an intricate web of values spread throughout files.

One example that comes to mind is the use of the $indent__[size] variables. If I changed the value of one of the variables, I suspect many things on the site would break. While they may seek standardize spacing, if there are no rules to recommend which size to use in which instance, they actually become merely abstracted values.

Another example is all of the shades of gray. Colors are often one of the best times to use variables. However, as it is, there are a little over 40 different shades of gray, along with many other colors, declared in variables and used throughout the site. When I need to set the colors of a theme I’m working on based off of a design prototype that I have for a site, I find myself wondering which values to change. Changing them all is tedious and usually well beyond the scope of the design prototype.

Variable overuse also leads to a unique problem: how does one name such a specific variable? Naming in the current stylesheets is quite verbose. Take this for an example: product-grid-items-per-row-layout-2-right-screen-s. This variable appears to define a value used for one specific screen size, in one layout context, in one output component. While long variable names are not bad in their own rite, I think they are indications of a problem. If a variable is used only one time, why declare the variable at all? With the advent of amazing browser developer tools, using a variable to simply name a property isn’t necessary, and instead, adds a step for finding it.

My suggestion: define a specific goal or requirement of variables and err on the side of less rather than more. Ensure that variables are only for values that are, and should be, repeated and intimately related to each other. For example, the goal could be: “only use a variable for values related to each other and that can be changed (within reason) without causing unexpected damage.” Local variables that are declared just before they are used have their place also, but from my experience that is not often.

Mixins or Extends

Both are used extensively in the Magento 2 frontend. This is also a tool that I think is very over-used. For example, when I opened the _pages stylesheet which handles the paging display, I was surprised to see approximately 100 parameters used for the mixin declaration. There are a number of other instances like this. On some projects, I found it so tedious to work through the parameters that I wanted to change, that I removed the mixin in my override. One file went from about 400 lines of source code to less than 100. That’s a lot easier to maintain! The goal of using mixins and extends was good in that it theoretically allowed us to reuse those components. However, in practice, when the mixins are that large, it seems to me that it would be just as easy to reuse the class names to replicate the desired end goal.

But there’s another problem with mixins: when they are so large, there are wasted properties and not enough properties. What if I want to add a box-shadow but there is no parameter that lets me pass in the value? Or, what if I don’t need a gradient, yet it’s declared in the mixin, and sent to thousands of users over time with an inherit value?

Extends are used frequently. There are some incredibly long selectors that are output due to their use. Here is a real example:

.abs-account-blocks .block-title>strong,
.abs-account-blocks .box-title>span,
.abs-block-title>strong,
.account .column.main .block:not(.widget) .block-title>strong,
.account .column.main .block:not(.widget) .box-title>span,
.block-compare .block-title>strong, 
.block-giftregistry-results .block-title>strong, 
.block-giftregistry-shared-items .block-title>strong, 
.block-reorder .block-title>strong, 
.block-wishlist .block-title>strong, 
.magento-rma-guest-returns .column.main .block:not(.widget) .block-title>strong, 
.magento-rma-guest-returns .column.main .block:not(.widget) .box-title>span, 
.multicheckout .block-title>strong, .multicheckout .box-title>span, 
.paypal-review .block .block-title>strong, 
.paypal-review .block .box-title>span, 
.sales-guest-view .column.main .block:not(.widget) .block-title>strong, 
.sales-guest-view .column.main .block:not(.widget) .box-title>span, 
.widget .block-title>strong, 
[class^=sales-guest-] .column.main .block:not(.widget) .block-title>strong, 
[class^=sales-guest-] .column.main .block:not(.widget) .box-title>span, 
h1, h2, h3, h4, h5, h6 { ... }

There is a very large file (_extends) that contains many abstract definitions which are extended other places. Extends are quite difficult to manage because they can drastically change the source order of declarations. As seen in the above example (with :not(.widget)), small additions to the source code can balloon into massive changes in the output. They also go against the core idea of an object-oriented approach to CSS (discussed more further on) by conglomerating many unrelated blocks in the output.

Another significant problem for mixins and extends is the time it takes to track down the original declaration of a property. As it stands, there are mixins inside mixins, mixins inside extends, and extends inside extends. Tracing through the source, looking for the base declaration is something that even source maps don’t help with and is a remarkably unpleasant experience.

My suggestion: use mixins only for small, reusable, single-responsibility blocks of styles. The current media query mixin is a perfect example of this. Also, Harry Roberts has an interesting article on CSS Wizardry about why mixins are better than extends due to performance. In my opinion, extends should be almost completely eradicated from the code base. For the few times where they may be a reasonable choice, and if SCSS is used, the %classname syntax should be used over .classname for an abstract definition.

Selectors

The selectors in Magento 2 are actually better than Magento 1, but not by much. Selectors are nested freely even when they don’t need to be. There is no standard naming convention in place, and selectors are broken apart by preprocessor nesting. To make it worse, the & is used to break up single class names. Overriding a declaration often becomes a specificity war. I think almost everyone agrees that this is not ideal, which is great.

My suggestion: fixing this is not quite as easy as it may sound. From my experience, using a naming convention is hard because it forces a developer to come up with a name for everything. And we all know that naming is hard. However, the end result is certainly worth the effort. I personally use and like the BEM naming convention. It is one of many, and the exact one chosen matters far less than how well the system is implemented. The important part is that the author of the stylesheets fully understands what it means to write object-oriented CSS and the principles related to that. For example, a few of these concepts include the following:

  • Encapsulation: ensuring that styles don’t leak into or out of the object.
  • Naming: choosing a parent class name that is appropriate, understandable, and flexible.
  • Single responsibility: determining when to put a child component into its own object versus include it with the parent.
  • Convention: adherence to the naming convention. For example, not doing this in BEM: block__child__grandchild.

Also, I personally have no problem with preprocessor string concatenation being used for selectors. The problem is when it is done ad lib and outside of clear rules. For example, I often split “elements” out inside of a “block” (based on the BEM naming convention) but rarely beyond that.

Partial Size

Currently the _module partial is a rather large file that encompasses almost all of the output components for that Magento module. There are some exceptions, like the checkout and catalog modules, but in general, the _module file tries to achieve too much.

My suggestion: I feel that using a naming convention (like BEM) will ultimately solve this problem because each object will be in its own partial. There could be cases where the block is so small that it hardly merits its own partial. I doubt there will be a clear cut way to determine what is too small or too large but can picture general guidance being written for this.

CSS Units

Aside from some layout declarations, Magento 2 stylesheets use almost strictly one type of length unit: px. The px unit is used for font-size, line-height, spacing, and such. From my experience the px unit tends to be used too much in CSS in general because it is easy to understand and predictable. However, it is very brittle. For example, if a user zooms in on the screen, the media queries don’t work correctly. Aside from accessibility disadvantages, the px is not relative to the parent element requiring responsive adjustments to be needlessly verbose.

My suggestion: use more relative unit types. As I understand it, CSS has about 30 different length unit types. The important relative units include em, viewport-based units, and rem. I try to follow these very-general guidelines when declaring values:

  • Use px for border-width, or other values that would be low values (1-2px).
  • Use em for padding, margin, border-radius, and font-size on child elements.
  • Use rem for font-size of parent elements in particular, or anything else that should be relatively fixed.
  • Use viewport units (vwvhvminvmax) where possible, or in conjunction with other units. For example: margin-top: calc(1em + 1vw).

When relative units are leveraged effectively, it drastically increases the flexibility of a layout. Also, on this note, the current percentage widths are good, and units such as color hex codes seem fine.

Context-Dependent Styles

Magento has the option to set the page layout on a per-page basis. While this is great, it adds complexity to many of the layouts. While better usage of relative units and consideration of more flexible ways to handle layout helps with this, there are inevitably going to be times where context-dependent styles are required. As it is, body classes are nested within the parent selector to adjust the style as necessary. The catalog module’s _listings partial contains a number of examples like that.

My suggestion: what about creating a mixin that would handle the classes? Perhaps @include layout($columns-three) {}. While I haven’t prototyped this myself, it would have a unique advantage of allowing developers to disable certain layout styles to decrease the size of the stylesheets somewhat. In my experience, not all websites utilize all layout options, and this would be an easy to way to clean that up some. It is a small detail, but I could see it being helpful. One thing that I see as important, though, is keeping the contextual declarations in the same block as the rest of the styles. This allows developers to get a complete view of a component without looking through other files.

Media Queries

Currently media queries are added to the bottom of the file in single declarations with many selectors inside. There doesn’t seem to be a standard on whether the primary styles are intended for smaller or larger screens by default. The comments in the files are confusing because they refer to specific devices. This makes it unclear where to add a new media query for devices that may be in between “mobile” and “desktop,” or for very large screens. By having the media queries separated to the bottom of the file, it requires developers to search through multiple places to find all the properties for a particular element. This increases the amount of time it takes to update styles as well as the likelihood that something will be missed in the process.

My suggestion: add media queries directly into the base definitions. This is one area where preprocessors hold a clear advantage. Media queries can be nested like this:

.class {
  font-size: calc(1rem + 1vw):

  @media (min-width: $screen__s) {
    font-size: 1.5rem;
  }
}

By moving everything together, all the style declarations can be seen at once without searching to the bottom of the file. Selectors are not duplicated which keeps specificity the same for all media queries, and it demonstrates which media queries have precedent (by source order). Thanks to gzip compression, the differences in output have been proven to be negligent. Also, avoid using the terms “mobile” and “desktop” and focus on screen size like the variables do.

Defaults

Style declarations for HTML elements seem fairly aggressive in the current stylesheets. I’ve found myself resetting margins on lists multiple times, or overriding the height: auto property on the img tag.

My suggestion: this is certainly a mixed bag. Element defaults have their place, but in my experience, less is often better. One thing I suggest to combat this issue is to work toward scoped CMS areas. This allows those defaults to apply to areas where they are helpful while not getting in the way of other components. Ultimately, defaults are an important part but should be handled with great caution.

Icon System

The font icon system that is bundled with Magento 2 works fine. However, it is a font icon system and comes with the inherent downsides associated with that. Font icons have very strange sizing details, require a more involved process to modify, and have limited control by CSS. With browser support where it is, there is no need to use an approach that was a workaround for not having access to a true vector image format.

My suggestion: switch to an SVG icon system. This would be a significant change, but it is an important one so I wanted to put this out there. The benefits of using an SVG icon system are quite clear with better accessibility, fantastic CSS control, ease of editing and adding to the icons, and natural sizing. SVGs are easy to understand with a code format that is human readable to an extent. There are other advantages in addition to those I’ve briefly noted here.

There are a few hurdles that would need to be crossed (namely, Internet Explorer and Edge) but nothing very difficult. I built an icon system for Magento 1 which is running a site with a large quantity of icons. I think it would be particularly amazing to be able to include SVG icons within modules like we can with CSS.

New CSS Properties

This is at the end because it is very minor, but it would nice to ship with some of the new CSS properties that are available on most browsers now. The most obvious is display: grid which would reduce the amount of code necessary to layout product grids. A fallback would still be necessary, but the @supports feature detection query allows these new properties to be used easily.

Wrap up

I am excited to see what is ahead for Magento 2. While I listed my current pain points, along with possible solutions, I think the current architecture provides a solid structure with which to build on. The community is also eager to assist with these improvements after the direction has been set. Many of the concepts listed above are also reasonably simple but would need to be communicated to those working on development and enforced in some measure in order for them to be effective.

Jesse Maxwell

COO / Senior Developer at SwiftOtter - @bassplayer_7