Separation of concerns

Published

Software development dogmas can take a long time to die.

Take Separation of concerns, an important principle of computer science, which, if not understood properly, leads to programs sectioned along inefficient, sometimes arbitrary lines.

This confusion can be seen in a type of recurring comment, an unfunny and unintentional meme, that we see in online discourse. It goes something like that:

  1. HTML, CSS and JS being distinct languages, they are separate concerns.
  2. Modern component frameworks are bad, because they break this rule.

Now, let me preface this discussion by noting that it specifically applies to web application development.

If you’re not building a highly interactive application, in a component-driven way, your concerns might be different than the ones described here.

With that out of the way, are the concerns of Structure, Style, and Interactivity really separate?

Components in other languages

If one were to design a language from scratch today, whose purpose is to build interactive applications, would they divide it into three? One language to create the structure of the component, another to style the component and one more to define its behavior?

I doubt anybody would think this is a good idea. Sounds pretty damn dumb if you ask me.

Yet, we find people claiming that this is how web apps should be built, supporting their argument with the fact that HTML, CSS and JS are three different languages, thus three different “concerns”, as if oblivious to the historical context of how web technologies came to be.

The web is HTML, CSS and JavaScript because it evolved from a read-only document sharing system, that later gained its styling and interactive capabilities, not because those technologies are the optimal UI development paradigm.

Let’s use a modern example by looking at SwiftUI, a somewhat recent approach to building user interfaces. Here’s some code pulled from their docs:

Button(/* */) {
  Label("Refill Water", systemImage: "arrow.clockwise")
    .foregroundStyle(.secondary)
    .frame(maxWidth: .infinity)
    .padding(.vertical, 8)
    .padding(.horizontal, 12)
    .background(.quaternary, in: .containerRelative)
}

You’ll notice that the notion of separating styling from the component structure is absent here.

Now, I’m not pointing to SwiftUI as the definitive source for UI development. My own experience indicates that this approach is more efficient than separating structure and style.

I’ve seen monolithic CSS files climb to thousands of lines, and methodologies such as BEM to try and rein in the chaos, but nothing is as simple and elegant as component-scoped styles.

Presentation as a specific concern

Different frameworks handle styling differently, but most embrace the idea of component encapsulation.

Having external styles impact your components in a cascade, originating from outside to dictate the appearance of child elements makes reasoning about, and debugging styles more challenging, requiring the developer to constantly keep extra context in mind.

If you find yourself asking “Where is this CSS coming from?”, there’s an issue with your architecture. Your concerns are so well separated that you have no clue what’s going on.

HTML, CSS and JS serve the same concern: presenting data. This is why frameworks like Vue and Svelte bundle them into single-file components.

While React doesn’t prescribe a styling approach, and uses JSX rather than SFCs, the popularity of CSS-in-JS and Tailwind is undeniable.

Style encapsulation at the component level is more efficient and easier to work with, especially as the component library grows.

Let’s take a look at an example, this time using Theme UI, described as “a library for creating themeable user interfaces based on constraint-based design principles.”

To keep things simple, I’m pulling the example from their front page:

// Create your theme

import type { Theme } from 'theme-ui'

export const theme: Theme = {
  fonts: {
    body: 'system-ui, sans-serif',
    heading: '"Avenir Next", sans-serif',
    monospace: 'Menlo, monospace',
  },
  colors: {
    text: '#000',
    background: '#fff',
    primary: '#33e',
  },
}

// Style your UI

/** @jsxImportSource theme-ui */
import { ThemeUIProvider } from 'theme-ui'
import { theme } from './theme'

export const App = () => (
  <ThemeUIProvider theme={theme}>
    <h1
      sx={{
        color: 'primary',
        fontFamily: 'heading',
      }}
    >
      Hello
    </h1>
  </ThemeUIProvider>
)

Notice how style information is defined as data in the theme, which is then used in the JSX template via the sx prop.

Again, I’m not advocating for a specific library here; I’m using this example to illustrate that web technologies collectively address the same presentation concern, which includes both structure and style.

Conclusion

In the era of component-based UI frameworks, structure, style, and interactivity are not individual “concerns” that should be separated. They collectively serve the same fundamental presentation concern.

Web technologies evolved into what they are today to bring new capabilities to the platform. We shouldn’t limit ourselves to a dogmatic separation of those technologies that idolizes a perceived purity over practical efficiency.

Good abstractions should be discovered, rather than created. As you work in a codebase, its different concerns become readily apparent. In a web application, they may be things like data fetching, state management, and business logic, not the artificial boundaries between HTML, CSS, and JS.

So, the next time you hear someone mention “separation of concerns” as if the phrase itself were an argument, feel free to challenge them and ask what concerns they’re trying to separate. Chances are they haven’t put much thought into it.