True decoupling of webcontent and -styling

Ivo Herweijer
13 min readJul 31, 2018

--

Photo by Pixabay

Can we achieve true decoupling of content and styling in webpages and web-applications? That is the question I would like to explore and maybe answer in this article. The first half of this article examines several approaches for styling the web. The second half of this article focuses on webapplications created with the Ferro framework.

This article is intended for webdevelopers in general. I am assuming that terms like HTML, DOM, CSS, classes, selectors, inheritance need no explaining.

Goals

What are the main goals in webdevelopment when it comes to styling webpages and -applications?

  1. Decoupling content and styling (separation of concerns)
    Decoupling means that in an ideal situation a change to the content of the webpage requires no changes to its styling. And vice-versa a change in styling requires no changes to the content of the webpage.
  2. DRY
    As a developer you (D)on’t want to (R)epeat (Y)ourself. You only want to write a piece of code or markup once and refer to that whenever it is needed. This leads to a smaller and easier to maintain codebase.
  3. Consistency
    Ensuring that all elements across a website have the same look and feel. Making site-wide styling changes easy to implement. Adding new content can make use of existing styling.
  4. Easy development
    Spend a minimal amount of time on basic things like styling common elements or solving cross-browser issues.
  5. Easy collaboration
    Developers can work independantly on content and on styling, with minimal dependancies during development.

First let’s have a more in depth look at decoupling. The decoupling described above is what I call ‘true decoupling’. Changes on the DOM side have no effects on CSS side and the other way around.

As pointed out by Adam Wathan, in a HTML/CSS world there is always a directional dependancy. Either HTML depends on CSS or CSS depends on HTML. For instance when using the well known Bootstrap CSS framework, your HTML heavily depends on CSS. You have no free choice of classnames and often you have to include container HTML elements to accomodate Bootstrap.

Approaches

Consider a traditional serverside rendered HTML page. HTML Elements may have a reference to use one or more CSS classes. A CSS stylesheet defines rules for these classes. There are several approches on how to define and combine classnames. Lets examine how each approach scores on the five goals.

Semantical

The oldest is the semantical approach. Every HTML element is given a classname matching its logical function within the page. Every classname is defined in the stylesheet and has some styling rules. Stylesheets are completely dependant on classname defined in HTML and are almost a mirror image of HTML. Changes to content that introduce new classnames mean changing styling changes. No decoupling here.
We may achieve DRY-ness if we make use of the cascading nature of stylesheets. General styling rules can be applied to the HTML types. Specific rules are applied to classes. I think this approach is closest to the way the original designers of CSS envisaged it use.

Decoupled

There are a lot of newer design methodologies that more or less succeed in decoupling content and styling. OOCSS, ACSS, SMACSS, DRY-CSS and BEM to name a few. The last one, (B)lock (E)lement (M)odifier is perhaps used most widely. BEM imposes a strict naming scheme on the HTML side. Every HTML element gets a classname composed of its functional role (the block) and part within that role (the element). Optional modifier classes can be assigned to a HTML element. Once the HTML is created this way styling changes should never lead to content changes. So a big yes on (directed) decoupling.

The CSS defines styling rules for every block, element and modifier. Downside is that when two blocks look the same but have a different function you end up with identical definitions in your CSS.

Other decoupled methodologies often use (long) lists of CSS selectors to assign uniform styling to HTML elements. Either way these methodologies don’t score well on the DRY-ness goal.

Functional

Functional a.k.a. ‘Utility-first CSS’ is an approach that reverses the direction of decoupling. The CSS sheets define a (long) list of styling elements as classes. Every class has a ‘utilitarian’ role defining just one aspect of styling, for instance colors, borders or margins. On the HTML side you can choose from this curated list of classes, assigning one or more classes to every element. A button might look like this in HTML:

<a class="f6 link dim br2 ph3 pv2 mb2 dib white bg-black">

This is an example from the Tachyons framework. If you use a lot of buttons this would mean a lot of duplication of classnames in your HTML. So the next step in using a utility first methodology is combining often used combinations into separate classes.
This approach achieves directed decoupling, changes in content should never lead to changes in styling, but doesn’t score high on DRY-ness.

Frameworks

All approches mentioned so far have one thing in common: you define most of the styling rules yourself. This is a lot of work to get it working the way you want on all browsers and devices. It is tempting to use a ready made CSS framework to get a flying start. The popular Bootstrap framework is a good example.

For every type of HTML element Bootstrap provides a set of classes that you can use. A button can be as simple as:

<button type="button" class="btn btn-primary">

The HTML is completely dependant on classnames defined in the framework. But content changes shouldn’t require styling changes, so like the functional approach we can achieve directed decoupling. DRY-ness is limited since we have to repeat the same list of classes for every similar HTML element. And it gets worse when we try to change the default look of the framework. Often we end up overruling some Bootstrap CSS rules and introducing new classnames in HTML to add specific styling rules to, thus losing decoupling.

Comparison

In the table below I have listed how each approach scores on the five goals. Direction means which side mostly dictates the class naming scheme.

Looking at this table it is remarkable that decoupling and DRY are never both checked. While this table is by no means a mathematically soond proof, I think that we can have decoupling of either HTML or CSS, but it comes at a price:

We can’t have markup that is both decoupled and DRY at the same time !

At least not in a HTML and CSS only world.

Front-end frameworks

Lets leave server rendered HTML behind us and enter the world of front-end frameworks. Internally a webbrowser only knows about the (D)ocument (O)bject (M)odel. This is a memory structure that the webbrowser can render and show to the user. HTML is one way of telling the webbrowser what the DOM memory structure should look like. Another way is to create the DOM using its progamming interface. Javascript code can create DOM elements as well as HTML can. Plus javascript can manipulate DOM elements at runtime.

Javascript front-end frameworks use this programming interface. Most frameworks contain a sort of HTML-templating language. This extends HTML with the possibility to create hooks for javascipt code, giving the developer easy access to DOM elements from javascript code and the other way around. Using this technique the framework can update (parts of) the DOM at will.

But all updates to the DOM are still based on HTML. This means that all of the approaches we have seen earlier and their properties still apply. Client-side rendered HTML doesn’t magically offer us separation of concerns.

Abstraction

So how can we turn directed decoupling into true decoupling. The fundamental theorem of software engineering tells us that all problems in computer science can be solved by adding another level of indirection. In this case we need an abstraction layer that sits between content and styling.

Currently the only solution I know of that allows adding an abstraction layer is the Ferro framework (coded in Ruby). The rest of this article shows how the decoupling problem is solved using Ferro. Disclaimer: I am the author of Ferro.

Ferro

A front-end framework that doesn’t use HTML templates is Opal-Ferro. You can read more about Ferro here. In short Ferro is a framework that focusses on creating a (M)aster (O)bject (M)odel. The developer only writes code to create and interact with the MOM. Ferro takes care of creating and modifying the DOM.

Ferro is coded in Ruby, which is transpiled into Javascript by Opal. Using Ferro means you don’t have to write a single line of HTML. Ferro doesn’t use HTML templates, javascript hooks or javascript specific classnames. You don’t even specify element id’s or CSS classnames. Instead you only focus on the structure of the webapplication.

Styling Ferro applications

Originally Ferro only supported a semantic approach for styling elements. When the MOM creates a DOM element, the Ruby classname of that element is added to the DOM element. In addition the name of the Ruby superclass is added to the DOM element. For instance if the MOM specifies a form-button with this class definition:

class MyMenuButton < Ferro::Form::Button

a DOM element will be created with this HTML representation:

<input type="button" class="my-menu-button ferro-form-button">

In the CSS for the application you would add generic button styling to .ferro-form-button and specific styling to .my-menu-button.

If you need dynamic styling for an element you can add a state to the Ruby MOM element. By setting or resetting that state a CSS class is added or removed from the associated DOM element.

While this approach to styling works very well it still doesn’t do a great job at decoupling content from styling. Trying to use a CSS framework like Bootstrap is also a little awkward.

Composition

The latest version of Ferro (0.10.2) contains a component called the ‘style compositor’. What this does is provide a mapping between Ruby classnames and CSS classnames and is in fact the abstraction layer between content and styling mentioned above.

Instead of adding just the Ruby (super)classname to a DOM element, the compositor can add a list of CSS classnames to DOM elements, for both the Ruby class and its superclass. This vastly expands the flexibility of styling Ferro applications. Moreover the compositor can offer true decoupling of content and styling. Changing something in the MOM doesn’t require changes to CSS. Styling changes in CSS do not require changes to the MOM.

Content or styling changes might require adjustments to the compositor mapping. But these are usually simple or reuse existing mappings. Plus you get to use all of Ruby’s amazing programming power to define mappings.

Before showing what compositor code looks like I would like give a schematic overview of decoupling:

At the top the directed decoupling of the semantic approach and the framework approach are shown. Either HTML depends on CSS or the other way around.
Below that the Ferro approach is shown with its abstraction layer called the compositor. There are no dependencies between content and styling.

Rules

So what does compositor code look like? A simple example might look like this:

class AppCompositor < Ferro::Compositor  def map(theme)
{
'Document' => %w{bg-light},
'LayoutContainer' => %w{container-fluid},
'LayoutRow' => %w{row},
'Menu' => %w{col-12 col-sm-3 col-md-2 mt-3},
'Todo' => %w{col-12 col-sm-6 col-md-5 mt-3},
'Aside' => %w{col-12 col-sm-3 col-md-4 mt-3},
'Ferro::Form::Button' => %w{btn btn-sm}, 'DarkButtons' => %w{btn-dark},
'SearchButton' => 'DarkButtons',
'SubmitButton' => 'DarkButtons',
}
end
end

In this bit of Ruby code a compositor object is defined. All CSS is default Bootstrap 4.
The AppCompositor inherits from Ferro::Compositor. For our application we only need to define the mapping of Ruby classes to a list of CSS classes by overriding the map function.
The map function should return a Hash, which in Ruby is a sort of key-value store. At runtime when Ferro starts creating DOM elements, the compositor receives the Ruby classname defined in the MOM as input. This classname is looked up in the hash keys. If found the corresponding value is returned as a list of CSS classes and added to the DOM element.

So if this class is defined in the MOM:

class Menu < Ferro::Component::Navigation

a DOM element will be created with this HTML representation:

<nav class="col-12 col-sm-3 col-md-2 mt-3">

The same process will be repeated for the Ruby superclass. So when this class is defined in the MOM:

class SimpleButton < Ferro::Form::Button

a DOM element will be created with this HTML representation:

<input type="button" class="btn btn-sm">

This makes it possible to add generic styling to all buttons.

Suppose we have multiple buttons on our page that need specific styling. We can use a feature of the compositor. If a mapping returns a string value (instead of a list) the compositor will look up that string value in the mapping and use the returned value as CSS classes. So when this class is defined in the MOM:

class SubmitButton < Ferro::Form::Button

the DOM element ends up looking like this:

<input type="button" class="btn btn-sm btn-dark">

Change CSS Framework

The previous examples used Bootstrap for CSS. We can easily swap Bootstrap for another framework, for instance Tachyons:

class AppCompositor < Ferro::Compositor  def map(theme)
{
'Document' => %w{
near-black bg-washed-blue sans-serif
},
'LayoutContainer' => %w{mw9 center ph3-ns},
'LayoutRow' => %w{cf ph2-ns pa3},
'Menu' => %w{
fl w-100 w-20-ns w-25-m w15-l pa0 mt3
},
'Todo' => %w{
fl w-100 w-60-ns w-75-m w70-l pa0 mt3
},
'Aside' => %w{
fl w-100 w-20-ns w-100-m w15-l pa0 mt3
},
'Ferro::Form::Button' => %w{
button-reset f6 b link dim bn ml2
ba br2 ph3 pv2 mb2 dib
},
'DarkButtons' => %w{white bg-black},
'SearchButton' => 'DarkButtons',
'SubmitButton' => 'DarkButtons',
}
end
end

As expected no changes are needed to the content of the application (the MOM). Only the compositor mapping is changed. In fact the left hand side of the mapping is exactly the same as the Bootstrap version (only reformatted a bit because of longer lists of classnames needed for Tachyons). In other words: true decoupling.

It might seem like a small step, moving the list of CSS classes from each HTML element to the compositor, but this separation makes a world of difference with regards to decoupling and DRYness.

Other styling approaches

Using a semantic approach to setting up CSS classes is possible but doesn’t make a lot of sense. Every line in the compositor mapping would map to a single CSS class. Changes to the content would involve changing both the compositor mapping and the stylesheets, which defeats the purpose of adding an abstraction layer.

Decoupled methodologies might work. If the naming of Ruby classes to define the MOM uses functional names you will most likely already have ‘block classes’ and ‘element classes’. In the compositor these names can be mapped to correct CSS classes. And in the mapping you can add any modifier classes. The use of CSS selectors, as used in some methodologies, should really be a no go area.

The use of a functional or framework approach in a Ferro based application to me makes the most sense. It means less code to write and gives you true decoupling.

Leaky abstraction

It is said that ‘All non-trivial abstractions, to some degree, are leaky’. So where does the style compositor leak? When a DOM element needs another element to be present to create a desired effect this element must be added to the MOM. For instance for Bootstrap columns to work you need parent row- and container elements. In the MOM these elements have no use so you would rather not include them.

Themes

You may have noticed the theme parameter in the compositor map function. You can make the mapping produce different results depending on the value of theme. Create a light theme and a dark theme for instance. A very simple example af a compositor with two themes might look like this:

class AppCompositor < Ferro::Compositor  def generic
{
# Add generic mappings here
'Document' => %w{sans-serif},
}
end
def theme_dark
{
# Theme dark specific stuff goes here
'Document' => %w{white bg-near-black},
}
end
def theme_light
{
# Theme light specific stuff goes here
'Document' => %w{near-black bg-white},
}
end
def map(theme)
generic.merge(
case theme
when :dark
theme_dark
when :light
theme_light
else
{}
end
) { |key, a, b| a | b }
end
end

Calling map(:dark) will produce: {"Document"=>["sans-serif", "white", "bg-near-black"]}. Other coding styles to create themed mappings are possible. You can use the power of Ruby here: dynamically load themes from the server or calculate a theme from some parameters. Go crazy.

As a free bonus functionality the compositor lets you swap themes at runtime. Call switch_compositor_theme(:dark) to switch to the dark theme. For all DOM elements Ferro will remove the CSS classes not used by the dark theme and add any classes that are used.

Goals

If we look at the goals table again with the compositor added it now looks like this:

* You may have to deal with the occasional merge conflict on the compositor file

Example app

If you want to play around with Ferro and the style compositor you can use this sample application. Follow the instructions in the README file on how to get started. This application implements a simple todo list. You can use either Bootstrap or Tachyons for styling.

Conclusion

We have looked at possibile approaches to styling webpages and -apps and examined the pro’s and con’s of each approach. With the ‘startling’ conclusion that we can’t keep content and styling both decoupled and DRY at the same time.

Next we looked at adding an abstraction layer between content and styling. This is (as far as I know) only possible after letting go of HTML as the delivery medium for web content, but offers true decoupling whilst keeping all code and markup DRY.

--

--