CSS at Scale — Public & Private Styles
Improve the versatility of your shared UI components and empower non-developers to be style authors
I’ve previously written about a similar concept — separating styles depending on whether they relate to cosmetics or layout. This article can perhaps be considered an evolution to the initial idea. Rather than thinking of styles in terms of cosmetics and layout, instead they can be thought of as public and private. At this point it has to be clarified that the terms “public” and “private” are being applied at the component level (or modular level). Meaning, that a (UI) component could have either public and/or private styles.
The fundamental philosophy hasn’t changed since the original article — if a CSS property is prone to changing based on the implementation of the component in some different context, then it can be considered a public style of the component. If a CSS property is not prone to changing, ever, regardless of how the component is used, it can be considered a private style of the component. In practice, it would almost certainly work out that public styles correlate to the component’s look and feel, and whilst some private styles may also contribute to the look and feel, it would also almost certainly work out that all styles affecting the layout would be private (why would a component need a position of relative
in one context, but absolute
in another, for example? I can’t fathom it).
My question is — why would you want to keep both of these sets in the same location? Traditionally, the thought process might be “I need to change the component’s styles, so I will go to the component’s style file”. Makes sense, right? But really, there could be two main reasons as to why you are wanting to change some existing component’s styles. The first, is because you have found some bug or are developing a new feature — the component is broken or incomplete. The second, is because you are trying to make the component look different, perhaps based on some new requirement or different context (previously the background was blue, and now you want to make it green, or perhaps you want to add a border) — but it wasn’t considered “broken” or “incomplete” before.
To me, I see these as being two completely separate tasks, potentially solvable by two different realms. Whilst they are both fundamentally “changing the component’s styles” in a traditional sense, one of them can be considered development/coding, the other can perhaps be regarded as a “change of configuration” (more on this later). In many systems, you are free to tweak some configuration safely without requiring a new release. Imagine if you could safely change the look and feel of a product without requiring a new release? Perhaps by adopting a philosophy of private and public styles, you can. Could this even lead to a world where non-developers can author 100% of the public styles for a product, putting the “look and feel” into the hands of those who actually “decide” it? Well, that’s probably jumping the gun a little, but it’s fun to imagine.
Not a New Concept…
This isn’t exactly a new concept, by the way. “Naked UI Components” may be a term I have just now made up on the spot, but I have still seen them in the wild. Pure React Carousel is a really neat carousel component for React. Taken from the README:
My goal was to create a 100% ReactJS carousel that doesn’t try to impose structure or styles that need to be defeated in order to match your site’s design standards.
What the author is saying here, is that you have to style the carousel yourself to make it fit in with your system’s design standards. It doesn’t provide any out-the-box “theming”. So, does this mean that it doesn’t come with any CSS out-the-box? If you answered “Yes”, you’d of course be incorrect:
The components in Pure React Carousel provide the bare-minimum of styling and javascript required to function correctly as a carousel.
What’s this? “Styling”…required to “function” correctly? What’s the meaning of this? How could styling possibly be required for functionality? Styling refers to the “look and feel”, no? Thus, the crux of why I’m writing this article. We all know that “yes, styling can indeed affect functionality”, yet I very rarely see people actually discern styles which affect functionality from styles which don’t. We’ve seen from the pure-react-carousel
example that by having private styles you can create, share and theme UI components much more easily. Their versatility becomes dramatically increased.
There is a much more obvious example of this philosophy occurring in the wild as well (the philosophy being public/private styles). Have you ever been on Motherfucking Website? If not, you’re missing out. The point, though, is that despite the website not containing any CSS, there is still an obvious visual distinction between the elements on the page. This is because web browsers apply sensible default CSS properties to HTML elements. Whenever you style a naked H1 element in a new project, it’s not actually “naked” to begin with. It already has applied the browser’s private styles (the most useful property perhaps being display
, with a value of block
). Any styles you add on top could be considered “public” (as far as the client/browser is concerned, at least).
Summary so Far
When we consider the evolution of semantic HTML tags into abstract reusable React JSX components, we no longer need an amalgamation of <section>
, <div>
, <button>
HTML tags etc. We can provide even more semantics with <Slide>
, <Dot>
, <ButtonBack>
etc. (taken from pure-react-carousel
).
As a wise man once said: With more semantics, comes more private styles. Perhaps out-the-box the <Slide>
component is just a <div>
with a position
of relative
, and perhaps the <Dot>
component is just a <button>
with a border-radius
of 50%
(to make it a circle). These sorts of styles are private to their components — they may be required for the component’s functionality (like the position
property on <Slide>
), or they may be required for the component to have semantic meaning (like the border-radius
property on <Dot>
). Unfortunately, it’s still all very subjective at the moment (for example, what if I don’t want the carousel <Dot>
to be a circle? Why is it even called <Dot>
? Could it not be a square?).
Putting the Philosophy into Practice
This concept is totally different to component libraries like Bootstrap, or any other UI component library that provides any out-the-box theming. Having a default theme to override is fine in theory, but from my experience existing component libraries that offer “themable” components are not that extensible or versatile — you are actually quite limited in how you can “theme” the provided components before you have to actually write some nitty-gritty code.
As it turns out, people are actually a fan of versatility when it comes to theming their UI components. Tailwind CSS is a popular CSS framework for rapidly building custom designs. Taken from their homepage:
Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.
So, you can see that Tailwind CSS and the philosophy from this article have at least one common enemy: “Annoying opinionated styles you have to fight to override”. Tailwind CSS has tackled this problem in an interesting way. It puts the blame on the libraries themselves trying to do “too much” by coming with “all sorts of predesigned components like buttons, cards, and alerts that might help you move quickly at first, but cause more pain than they cure when it comes time to make your site stand out with a custom design”.
The one thing I don’t see with Tailwind CSS is its place within Component-Driven-Development. It seems really cool for prototyping entire pages all in one go, but for actually creating reusable UI components, I’m perhaps thinking it wasn’t really built for that.
To quickly jump back to the problem as I personally described it:
Component libraries that offer “themable” components are not that extensible or versatile — you are actually quite limited in how you can “theme” the provided components before you have to actually write some nitty-gritty code
To me, it feels like me and Tailwind CSS are saying the same thing — but I don’t believe we have to ditch the concept of providing out-the-box UI components to solve the problem of UI frameworks, and I think pure-react-carousel
is a good example of how it could be done (or rather, an example of a UI component that might be found in the component library of tomorrow). Clearly, people love the concept of having out-the-box components, giving rise to Bootstrap and every Boostrap knockoff that has ever existed, and clearly, people love versatility, flexibility and extensibility, giving rise to things like Tailwind CSS.
I dream of a world where we have both, and as a wise man once said: “dreams are my reality”, and I don’t think public/private styles and naked UI components are necessarily just the nonsensical ramblings of a bored UI developer stuck in lockdown, they could potentially also be the keys to the next wave of UI component libraries.
Coding vs Configuring?
At the start of the article I mentioned how changing a component’s public styles could be regarded as configuration as opposed to coding. This is more an extension of the concept proposed thus far. After all, if you are authoring styles with CSS, regardless of whether you are authoring private or public styles, you are still writing “code”, and you will still require a new release for any changes.
This is where the theory ends, and we must start looking at capabilities of the technologies available to us. How can I tell my system “I want Accordion Title elements to have a red background, unless the corresponding Panel element is open, then it should have a green background, unless the element itself is hovered, in which case it should have a blue background” without writing any code, and without the Accordion Title element having a pre-existing awareness of any “background” property for these decisions to be mapped to?
We can start by agreeing what differentiates writing configuration from writing code (CSS, in this case). I could simply propose “it has to be able to exist as JSON” and call it a day, but then some smart-arse would come up with something like this:
And my reaction would be just like yours:
…because whilst we can just about accept CSS-in-JS is a thing, CSS-in-JSON would just be an absurd concept…but low-key, it’s actually the solution in disguise, which would more resemble:
This isn’t “code”, it doesn’t contain any CSS, it is simply me telling my system what I want in as simple terms as possible. It just so happens that what is written can easily translate to CSS. We can feed this to some hypothetical Accordion component without the component’s source even containing the word “background”.
Of course, we would need some actual defined standard to adhere to when mapping stylistic decisions to strings in a configuration file; we need some rules over what language is allowed (e.g. “panel is active”), because after all we are still communicating with a machine (as is the case when you set any configuration). Fortunately, I have already drafted a standard that at least for me and my needs has made sense, it’s called Cell Query, and it provides language rules (CQ Expressions) to cater for any stylistic requirement I have ever faced.
So, why would you want this? Well, why wouldn’t you want to abstract CSS logic into something more readable? Why would you want .panel.open > &
over panel-is-open
? When you don’t have to worry about CSS’s intricacies and things like classes and cascading etc, not only is it less taxing on the developer, as I mentioned at the start of the article, you can also empower non-developers to have control over the product’s look and feel. The barrier to entry for the second example is much, much lower with all things considered. And to be clear, we are only talking about public styles here — private styles would still be authored as code, using something like CSS/Sass/what-have-you.
One-Nexus
One-Nexus is a framework/library I am building which aims to offer out-the-box UI components like buttons, cards, and alerts, whilst also only providing the bare-minimum of styling and javascript required for the components to function correctly.
I didn’t even have to come up with this description myself, I just copied and pasted sections from the Tailwind and Pure React Carousel descriptions and stuck them together. Follow my progress on developing One-Nexus, and checkout Lucid for the underlying framework that powers the One-Nexus components.