CSS at Scale: Cosmetic vs Layout Properties

Edmund Reed
Level Up Coding
Published in
8 min readJun 23, 2019

--

The Problem

There are fundamentally two types of CSS properties: properties that control layout/structure/functionality, and properties that control the cosmetic look and feel. Knowing how, when and why to recognise the difference can improve the maintainability of your product and the productivity of your team. Often, business requirements can be satisfied by modifying only properties which are considered to be cosmetic (think branding); given the nature of this concern it makes sense to separate it.

So often CSS properties are slapped into production without proper consideration which is why it’s difficult to achieve a maintainable and scalable CSS codebase. It would be too impractical to comment on/document every single CSS property within a UI module to explain its purpose (but every property should have a known purpose and known side-effects, right?), but with a sensible separation of properties they can be managed and maintained much more easily, allowing for a more “self-documenting” CSS codebase.

Consider a UI element with just two CSS properties:

.element {
display: flex;
color: red;
}

The difference between these two properties may seem trivial (they’re both just CSS right?), but changing one could render your app unusable, whereas changing the other cannot. How does this epiphany help us build better products? Once you realise that most CSS properties which account for layout and structure have discrete values, and that most CSS properties which account for the cosmetic look and feel have continuous values, it becomes clear that cosmetic properties are much more prone to variation (which actually makes sense).

When something can be separated into two distinct categories with one containing safe and variation-prone properties and the other containing dangerous and variation-resistant properties, it just makes sense to separate them. This contamination of concerns with regards to layout vs cosmetic properties only serves to impede productivity at scale — it’s like keeping your soda and your bleach within the same cupboard just because they both happen to be “liquids within bottles”.

The Solution

Let’s assume your project has some sort of UI modules folder with each module having a styles.scss file:

modules/
| |-- accordion/
| | |-- styles.scss

It would just be too impractical (and too easy) to suggest something like:

modules/
| |-- accordion/
| | |-- layout.scss
| | |-- cosmetic.scss

…(though it’s not actually a bad idea…), but a better way to solve the issue introduces a new way of thinking, and that is to view cosmetic CSS properties of your UI module as configuration, instead of logic/actual code (CSS). After all, for cosmetic properties to convey meaning, you don’t actually need inherent understanding of CSS (unlike layout properties); “color: red;” will mean something to anyone who speaks English; “display: flex;” will not. Again, this may seem like a trivial nuance that has no relevancy, but in practice it can ultimately lead to the difference between requiring a new release and not requiring one, given that cosmetic properties can directly correlate to business needs (you can imagine the business requesting something to be red in color, but never for something to be “display: flex;”) and can be applied confidently without requiring any regression testing.

So now that we’re thinking about our cosmetic CSS properties as configuration instead of code, our project structure might now resemble:

modules/
| |-- accordion/
| | |-- config.json
| | |-- layout.scss

…with JSON being a standard format for data/configuration. From a theoretical point of view this is great, but from a practical point of view it isn’t much use because Sass can’t interpret JSON out-the-box, and JSON can’t write to stylesheets. The technical “how-to” to achieve the goal set out by this article is somewhat out-of-scope (though it will be touched upon briefly); there would be a million different ways to achieve it technically, so the focus will continue to be on the theory. But just to throw it out there, all this theory is possible in practice using the Synergy framework from One-Nexus (specifically synergy-sass-importer and Cell).

The Separation

So far we’ve just looked at two CSS properties (display and color), but it should be easy enough to place any CSS property into either the "layout" or "cosmetic" category without much dispute (...much...), given the rule of thumb that properties with continuous values = cosmetic and properties with discrete values = layout.

There’s actually around 500 valid CSS properties, and the exact nature of whether or not the property should be considered “cosmetic” or “layout” related depends on the context in which it is used more than anything.

The main objective is actually to increase productivity by moving potentially configurable properties into configuration and separating them completely from properties that are unlikely to change frequently, or never — so the real question is “is this property likely ever going to be changed more than once?” or even “could this property be changed at any point to directly satisfy some business requirement?”. Consider whether a “non-technical” person could be given charge of changing the value of the property in question, and whether or not changing the value could break the product (earlier I perhaps implied that the display property should never be configurable as it could break the application; however there are still examples where it could make sense to have this property configurable, such as controlling the visibility of a promotional banner).

As a rough idea, here’s how some common properties may often be separated:

Cosmetic Properties

  • Color
  • Background-color
  • Background-image
  • Border-radius
  • Fill
  • Font-family
  • Font-size
  • Font-style
  • Font-weight

Layout Properties

  • Box-sizing
  • Clear
  • Display
  • Flex
  • Float
  • Overflow
  • Position
  • Vertical-align

Controversial Properties

Height/Width
It’s 50/50 whether the use of height or width could be considered a layout property or a cosmetic property; it totally depends on the context. After all, the business could certainly say something like "make the logo bigger", and rather than making a code change you could instead modify some configuration to increase the size of the logo. But then you must consider questions like "could increasing the size of the logo somehow break the layout? In which case should we allow the size to be configurable in the first place?" - and the answer to these questions depends on factors that would be unique to each project.

Padding/Margin
padding and margin are tricky properties to categorise; whilst their values are continuous, their purpose is often layout-related, and in other cases their values are likely standardised within your design-system and hence unlikely to be configurable at a modular-level. That being said, it's totally plausible that you might use the same module in two different themes except one theme requires the padding to be slightly larger; in which case having the padding property as a configurable value for the module makes sense.

Top/Bottom/Left/Right
The top, bottom, left and right properties are often used for layout purposes, e.g. to vertically center something you can do:

.vertical-center {
position: relative;
top: 50%;
transform: translateY(-50%);
}

…here, it wouldn’t make sense to ever change the value of top, despite the value being continuous. More often than not it's likely that these properties should be considered layoutproperties and not cosmetic, given the nature of how they are used.

Before/After Example

Let’s take a look at some source code before and after adopting this philosophy, taking an accordion module as the example.

This example uses the Cell library

For context, the markup for the accordion module that we will be styling is:

This example used the BEM naming convention

Before

modules/
| |-- accordion/
| | |-- styles.scss
The mixins used in this example come from the Cell library

After

modules/
| |-- accordion/
| | |-- config.json
| | |-- layout.scss

Having now moved the configurable properties into their own file, this may now look something like:

JSON can be imported into Sass with synergy-sass-importer

Now, without some sort of magic tool to write to CSS from this JSON file, you would still need to manually map the above values to CSS properties in your Sass file. Using synergy-sass-importer, importing the config.json file would expose the values to Sass under the $config variable as a Sass map, so they could be accessed with a map-get-deep function:

Now I’m sure you’ll agree at this point we’ve arguably taken a step backwards instead of forwards; we are still tightly-coupling cosmetic properties to the source code, we have just allowed their values to be configurable from a separate JSON file. In the process we’ve made the source code more jarring to look at.

Luckily, thanks to the Cell library, we can achieve that magic we required earlier. Cell can intelligently determine which properties from a module’s configuration should be treated as CSS, and to which elements they should apply. This means we can remove them from the source code, leaving us with:

JSON is made importable by synergy-sass-importer

Without the cosmetic properties contaminating the file, there are now approximately 85% less properties to worry about, and the remaining properties are much more easy to decipher. This is important because it’s this remaining 15% which has the potential to break the application.

To be clear about one thing: this is not “CSS-in-JSON”. Even if you can technically prove it to be the case, the way of thinking to arrive at that conclusion misses the point of our goal. What we have is configuration whose properties happen to correspond to valid CSS properties; the fact of which is taken advantage of by the tools we are using to automatically generate the CSS for the corresponding properties. Thinking of it as “CSS-in-JSON” implies you can add any property willy-nilly, which is not something the convention condones. It’s important you only ever add properties to JSON which can translate to cosmetic properties which can’t break the application.

If the concept still doesn’t make sense (or it still seems to be CSS-in-JSON), consider the same concept with the configuration keys slightly re-worded, and without the magic aspect of the JSON properties creating CSS. This might leave you with something like:

{
"accordion": {
"panel-color": "red",
...
}
}

Now in accordion.scss you might have something like:

Which satisfies the goal of being able to make the value configurable (good for the business!), but still leaves the source code contaminated with cosmetic properties (bad for readability!). For all intents and purposes (from a configuration point of view), this:

{
"accordion": {
"panel-color": "red",
...
}
}

…is really no different to this:

{
"accordion": {
"panel": {
"color": "red",
...
},
...
}
}

…except that with the latter, certain things can be inferred more safely which can then be taken advantage of in APIs. So what this philosophy allows for in theory is a product whose source code can be kept smaller, cleaner and more concise, whose configuration can be used to generate cosmetic CSS.

Conclusion

By treating CSS properties as two distinct categories (cosmetic and functional/layout), you can leverage certain benefits that can ultimately lead to an increase in productivity, as outlined by the article. Doing so only really makes sense at scale and if you have the right tools to handle the benefits leveraged (otherwise the process becomes counter-productive).

In summary, the main practical benefits to this theory include:

  • Reduce size of source code
  • Improve clarity of source code
  • Allow cosmetic updates to be more efficiently handled
  • Have more confidence when changing CSS properties about what may be affected (benefit of the separation of concerns)
  • Improve overall how CSS is used in your product by drawing more attention to individual properties

…which all contribute to the common goals of software development such as scalability and maintainability.

--

--

Design Systems Architect 🎨 UI•UX designer & developer 💻 I take front-end thought experiments too far 🧪 @valtech 💙