Functional CSS From A Pure UI Perspective

I have been reading an endless number of posts regarding CSS over the last couple of days. The sheer number of possible ways to style an application is interesting but also leads to opposing opinions on what the “correct way” is. This is especially interesting from an outsider perspective, as my interaction with CSS is very limited in my day to day work. So from an outsider perspective there seems to be a constant search for the “ideal way” to achieve this. Achieving this status quo is complicated due to the fact that web based development can mean traditional static HTML websites as well as complex applications with dynamic behavior nowadays.

I can’t really comment on any of the concepts, as I am not an expert on the topic. What I’m rather interested in, are the ideas revolving around “Pure UIs”. Shortly after re-reading Guillermo Rauch’s now classic Pure UI article, I came across functional CSS, also referred to as atomic or immutable CSS. Jon Gold wrote about it in Functional Programming, CSS, and your sanity, please read it if you haven’t, which in short can be summed up as f(css). The functional CSS ideas are also being pushed by the amazing work done by Brent Jackson and mrmrs, including their respective projects Basscss, Rebass and Tachyons, showing how this is applicable in the real world.

Before we continue, let’s recap what a pure UI is. A formula describes it best.

UI = view(state)

UI is a pure function of the application state. Every time the application state changes, we want our UI to reflect those changes. This also means that the same state input will lead to the exact same UI representation. The view function is not allowed to change the state directly in any way.

This model lends to the idea that you design your application by composing small view functions, which should do one thing and one thing only. By breaking our view into small parts, we gain the capability to change the ordering and structure by composing as we choose to.

These concepts have gone mainstream with the rise of React and the “virtual DOM”. The real strength of using an abstraction like the virtual DOM is not the performance gain but rather the possibility to abstract the representation of an element from it’s rendering. The fact helps us to ensure that the same data passed into our component will result in the same output. From here on out we will neglect the virtual DOM and seemingly speak about the representation, which can be a simple JSON object.

To get a better idea, let’s see how this might look like when we test our view function.

const Button = ({ id, children, ...other }) => 
<button id={id}>{children}</button>

When calling Button we get an object representing the to-be rendered element in return.

{
// ...
key : null
props: {
children: "click!",
id: 1
},
ref: null,
type: 'button',
}

So we have decoupled the representation from the actual rendering, which makes it simple to test and verify that the same data in, results in the same JSON object being returned. Based on the fact, that we can do this, enables us to write pure view functions. This has been pushed by the introduction of stateless functional components in React for example and parts of the community have been embracing these concepts for a long time.

But what does this have to do with CSS?

If a component is in charge of the representation, then it should also be in charge of how it is styled. The same concept that we’ve seen above, should also apply to the styling aspect. To bring back the functional CSS aspect, passing in the same style definitions, should result in the same styles being rendered.

If I want my button to have a background color blue, then I want to guarantee that blue gets rendered to the screen, without having to care if in any other parts of the application another button might have a different color. Neither should my button, being styled in this way, affect other buttons and the other way round. To give these concepts some life, let’s see what we can gain by choosing this approach.

Let’s begin with a small example. We want to be able to style our aforementioned Button component, but before we can do that we need to figure out how these definitions should be applied. Let’s think about this separated from any stylesheets for a moment. All we want is to decouple the style definitions, by breaking them down into the smallest usable units. Take a look at the following example.

const green = () => ({
color: 'green'
})
const f3 = () => ({
fontSize: '1.5rem'
})

What if green was a function that would return a single definition, { color: ‘green’ } in this specific case, and what if we could compose those definitions into something representing the complete look and feel of the button?

To continue this experiment, we will need a function that knows how to convert lone definitions into a full fledged object.

const createStyles = styles => mergeAll(map(f => f(), styles))

All that createStyles does, is map over the defined styles and reduces them all into a single object. To get back to our Button, this might look something like this.

const Button = ({ id, styles = [], children, ...other }) => 
<button id={id} style={createStyles(styles)}>
{children}
</button>

Calling our Button with the needed properties will return a JSON object containing the defined styles.

Buttons({ id: 1, children: 'click!', styles: [green, f3] })

{
// ...
key:
null,
props: {
children:
'click!',
id:
1,
style: {
color:
'green',
fontSize:
'1.5rem',
},
},
ref:
null,
type:
'button',
}

We don’t style applications by defining functions, but it should lend to the idea that 1.) I want to dynamically compose a given set of applicable definitions without having to define them explicitly and 2.) make sure that one button’s style definition doesn’t break any other components inside my application.

You might also be thinking that when calling green always returns the color green, why not apply the value green directly. There is one aspect that we want to keep in mind here. If we suddenly decide that green is actually #32CD32, we can easily do so by only changing the value inside the green function, instead of having to do so in the entire application.

So given the fact, that we don’t want to define any functions but use stylesheets to define our classes, we can achieve the same as above by defining green and f3 classes, that do the exact same thing.

.green {
color: green;
}
.f3 {
font-size: 1.5rem;
}

The concept remains the same. Instead of composing styles, we now compose CSS classes. We can throw away our createStyles function and write a new function cx, that returns a single string, for a given set of class names.

const cx = 
(...classNames) => join(' ', filter(identity, classNames))
cx(false, 'foo', 'bar', null, undefined, 'baz'))
// 'foo bar baz'
cx(active ? 'active' : false, 'foo', 'bar', ...)
// 'foo bar'

Our Button component is in charge of the look and feel as well as in charge of what actually can and can not be overridden by a consumer. Let’s extend on our example and see how this applies in the real world.

const Button = ({
color,
bgColor,
size,
fontSize,
fontStyle,
className,
children,
...other,

}) => {
  const classnames = cx(color, bgColor, size, fontStyle, className)
  return <button {...{ className: classnames, ...other }}>
{children}
</button>
}

We have defined a subset of possible class definitions that our Button component accepts. What we gain is the possibility to write more specific Button components via predefined class compositions. For example we might have a SubmitButton, a DangerButton etc. So that we don’t have to manually use the low level Button.

These ideas are not new and for a more detailed walkthrough watch the React JS Style Components talk by michael chan for example.

By choosing this approach, we gain another nice feature. We’re able to type the different style class definitions, ensuring that the correct classnames are being passed in.

// ...
export
type Color = 'red' | 'green' | 'blue'
export type BackgroundColor = 'bg-red' | 'bg-green' |'bg-blue'
export type Size = 'w1' | 'w2' | 'w3'
export type FontSize = 'f1' | 'f2' | 'f3' | 'f4' | 'f5' | 'f6'
export type FontStyle = 'i' | 'b'
export type TextDecoration = 'underline' | 'strike'
export type TextTransform = 'ttc ' | 'ttu'
// ...

To reiterate on our Button example, this enables us to define what possible values are acceptable.

type PropTypes = {
color?: Color,
bgColor?: BackgroundColor,
size?: Size,
fontSize?: FontSize,
fontStyle?: FontStyle,
className?: string,
children?: any,
}
const Button = ({
color,
bgColor,
size,
fontSize,
fontStyle,
className,
children,
...other,
} : PropTypes) : React$Element<any> => {
  // ...
}

The nice thing here is that we get instant feedback via the IDE, as soon as we try to pass in a non-defined classname.

But what about things like CSS Modules, BEM (Block Element Modifier) or “traditional” CSS?

Using CSS Modules is trivial at this stage, as we can extend our Button function to look up if the passed in classname is defined via the imported style object and even fallback to a default classname when needed.

// not tested, but you get the idea...
const getColor = key => Styles[key] ? Styles.key : Styles.color 
cx(getColor(color), fontSize, ...)

Regarding the BEM approach, yes, we could achieve the same from a pure function point of view. With the BEM approach as I understand it, you have to be very explicit with your definitions and naming. The drawback is that we lose the dynamic aspects. For example we can’t just combine green and width-large on the fly, we have to define explicit classes for every possible combination we want to have applied. The traditional CSS approach of defining global styles might not be the perfect fit, but maybe it’s best is to read the The end of global CSS post by Mark Dalgleish as well as CSS and Scalability by mrmrs for a more fine grained explanation.

Tradeoffs and limitations

With all that being said, there are limitations to this approach. Dealing with atomic units, that only do one thing, means that the documentation aspect becomes even more important than it already should be. This is also where style-guides come into the picture I guess. Take a look at the fantastic documentation in Basscss and Tachyons for inspiration.

As a developer you will have to know the classnames and, if you’re working with components, also have to know which properties can be overridden etc. Taking this approach also needs a clear definition of what is accepted and what is not. For example, just think about rounded-corners. How many possible cases do we need to cover for things like left-corner-radius-2, left-corner-radius-3 etc. All of this can become very complicated, if there is no common agreement of what the boundaries should be.

I’ll be writing more about the topic with Mustafa Alic, who is a UI/UX expert, and see how these ideas work when we start to compose small view functions into bigger ones, resulting into applications.


As previously mentioned, I’m not an expert regarding CSS. This has been solely written from a pure component view. If I’m mistaken on some aspects please add a comment.

Any questions or Feedback? Connect via Twitter

Links

Pure UI

Functional Programming, CSS, and your sanity

CSS and Scalability

The end of global CSS

React JS Style Components