The Art of CSS

CSS syntax, like the box-model, flex, and grid is relatively easy to learn, or at least lookup. But CSS semantics, like when to apply the cascade, inheritance, or custom properties is an art form. And to master this art form, we first need to understand them.

The Art of CSS

Voon Siong Wong, Technical Lead DiUS

As we know, CSS is simple not easy. The syntax is easy, but the semantics are harder; so Voon will be talking about how might use the cascade, inheritance and custom properties effectively.

Quick terminology refresher:

selector { /* declarations block */
  property: value; /* declaration */
  another : value; /* declaration */

The cascade deals with the way multiple blocks can define values for the same element; and the specificity of the selector determines what applies.

CSS Cascade Level 0001:

  • elements and pseudo elements
  • carry semantics
  • they are the low level of the design
  • lo-fi look and feel

CSS Cascade Level 0010:

  • classes, pseudo-classes and attributes
  • ARIA attributes increase semantic richness
  • medium fidelity

CSS Cascade Level 0100:

  • IDs
  • no semantic value outside the app
  • high-fi

CSS Cascade Level 1000:

  • inline styles
  • no semantic value
  • can complete the look and feel

Ideally these properties cascade constructively through these levels to build the final style. When selectors all declare styles for the same element, ther is destructive interference where one of the styles ‘wins’ and removes the others. These are not good and bad things, good CSS is a balance of both behaviours.

In addition the cascade is influenced by the source of styles:

  1. user agent
  2. user (eg. with browser extensions)
  3. author
  4. author !important
  5. user !important
  6. user agent !important (rare, possibly non-existent)

Next we look at inheritance. Because HTML is inherently hierarchical, inheritance is a natural part of how styling HTML works. Some values like color are inherited from parent or ancestor elements; others like display are not.

You can force inheritance using inherit, initial and unset.

Let’s talk about the Single Responsibility Principle. This is good in JavaScript, but what does it mean in CSS? Voon feels this means:

  • declaration blocks should be easy to read and understand without their output
  • use composition to build an element’s style
  • responsibility can vary by the role it is playing
.as-child {}
.as-self {}
.as-parent {}
.as-peer {}

If we break our CSS down this way, we can work out what CSS is doing without needing to see the HTML.

If we think about this more, CSS has the ability to describe hierarchy as well:

.foo {}
.foo > * {}

CSS and HTML are co-dependent documents – we should not really write one without the other. We can also choose whether to push the complexity into CSS or HTML.

You can have very simple HTML and use more-complex/verbose selectors to build up style – at its most extreme having absolutely no classes in the HTML. Or you can go the other way and make the HTML more complex/verbose – at the most extreme, using things like Atomic CSS or Tailwind where you don’t write your own CSS at all.

The art of CSS is finding a balance that works.

Finally let’s look at custom properties (aka. CSS variables):

  • defined like any other property
  • obey the same cascade rules
  • inherited by default
  • can provide a fallback

This enables data-driven styles; and fine control of how dynamic styles get applied. While this can look like inline CSS, it’s much more powerful when properties depend on other properties. The inline custom property tweaks a single value within a larger piece of CSS – your CSS behaves more like a function.

So when we talk about the art of CSS, really we are talking about finding a balance – eg. global first vs local first. Whether you decrease the scope as required; or increasing the scope as required. Neither really works alone.

CSS is inherently designed for consistent design systems – to support global-first, with decreasing scope and increasing specificity.

But as developers we tend to focus on components which are local-first, increasing scope as required.

Voon would encourage you to embrace the cascade, don’t run away from it. Apply the common case, then apply for the exceptions. Think about the relationships between elements and describe that in your CSS, and you will find you reduce the verbiage in your HTML. This allows you to use composition of roles to build the styles you want; and make readable code.

Finally, custom properties are much more than a replacement for SCSS variables. They enable data-driven design. Why not try creating a bar chart just with DIVs and custom props! |