As new user interface component frameworks are created and old frameworks are replaced with emerging technologies, methods for styling those components must change with them. Long gone are the days of creating a simple HTML component and importing a simple CSS file with corresponding class names. Concerns such as style leaking, local DOM manipulation and theming are driving changes to the approaches we take. With developer IDEs and the JavaScript language itself and extensions such as TypeScript becoming more intelligent and allowing auto-completion, should our styling approaches not give us the same? In this world of intelligent auto-completion, why should we expect developers to remember and replicate matching class names across CSS and HTML? Modern frameworks are adapting to this shift and there are many different solutions to the problem of making styling and theming more intuitive. Here we will explore some of these solutions.

The olden days

In simple forms, a component is just a block of HTML with some corresponding JavaScript and CSS.

<div class='button'>
	<span class='label'>Click Me</span>
</div>
.button {
	display: block;
	background: blue;
}

.button .label {
	color: white;
}

There are many problems with this approach in this form. The first main issue is that any other styles for a label class will likely affect our button label. The class names are brittle as they are manually entered in both the HTML and CSS and the colours are hardcoded, making any changes or theming more difficult to maintain. Sure, you could tidy this up by using something like a BEM technique, but this is really treating the symptoms of the issue rather than changing the underlying approach.

More recently, styling and theming an app was often achieved using a framework such as the highly popular Bootstrap and adding extra classes / structure to your components to bring them inline with the framework’s guidelines. Other popular approaches such as Material and Semantic have since been released and largely have implementations available for use within React, Polymer, and other frameworks.

Improvements could be made to the approach of base CSS using CSS preprocessors such as Less, Stylus, and Sass. These introduced a proprietary format of CSS including popular features such as mixins, variables, and vendor prefixing. The downside of using preprocessors is that developers must learn this format and the end result is still CSS, without solving many of the same problems including leaking styles across components.

What we are trying to solve?

So what problems are modern component styling approaches really trying to solve?

Style leakage

A common problem with using class names in global CSS is that you can have style clashes. If you have more than one node with the same class name, these nodes will receive the same CSS rules. You can strive to avoid this problem by nesting your CSS selectors and by following the principles of BEM, but this can often become very complicated.

Changes to designs

Changes to your designs when using vanilla CSS can be a challenge as you will likely end up using find and replace to change color, padding, margin and other properties across your CSS files. Preprocessors provide variables to make this easier, but then you are introducing a new proprietary CSS language into your codebase.

Theming

Vanilla or preprocessed CSS can be challenging to theme. I’ve often found myself going down the path of writing overly complicated CSS rule definitions with highly specific selectors to override the base styles of a component and this quickly gets messy. Especially when the toolkit you are theming changes their style definitions in the next release, and you have to use the browser debugging tools to inspect the CSS to identify any changes impacting your UI components

Class name mistakes

Keeping track of how class names are spelt, and which class names applying particular styles, can be frustrating and inefficient. One small spelling mistake somewhere in your codebase can stop styles from being applied and tracking down the cause can time consuming as this is not reported as an error.

The new way

There are many approaches used in modern component frameworks. These can largely be split into CSS, CSS-in-JS and inline-styles. We’ll stay clear of the latter option and concentrate on those that generate actual CSS stylesheets.

So lets dive in.

Polymer Styling

Polymer uses the cutting edge of available browser functionality largely using polyfills to cover the gaps in browser support. As such, the framework uses the same approach for its styling. Polymer styles are written using proposed future CSS syntax and make heavy use of css-custom-properties.

css-custom-properties allow you to create DOM level scoped variables that can be used to alter the CSS that is applied to a given element.

<style>
	:root { --title-color: green; }
	.warning { --title-color: red; }
	.title { color: var(--title-color); }
</style>

<span class='title'>Green Title</span>
<span class='title warning'>Red Title</span>

Polymer utilises this approach for both its component styling and its theming. A theme file will typically include a theme class wrapping a number of css-custom-properties that are then used to manipulate the visible styles at run time. The challenge with this approach is that css-custom-properties cannot be fully polyfilled at run time as any polyfill must account for the structure of the DOM when applying the correct CSS properties and variables to each node.

To get around this issue, Polymer has a comprehensive build time polyfill for css-custom-properties which has knowledge of the application’s DOM structure and applies calculated variables to the generated output. At this time, Polymer has deprecated the use of external stylesheets, so all styles must be written within the polymer element template.

Styled components

Styled components for React uses tagged template literals to create styles. They can be used to create predefined components to represent button, a and other HTML elements, or to add styles to any standard React component. A CSS stylesheet is created with class names which are passed to the component via the classname property.

import styled from 'styled-components'
const Button = styled.button`
	background: blue;
	display: block;
	color: white;
`;

These stye tags can return functions that receive component properties. For example, you could pass a property to your component to indicate if it was a primary button, this would be used by the styles.button to adapt the styles accordingly.

import styled from 'styled-components'

const Button = styled.button`
	background: ${props => props.primary ? 'green' : 'blue'};
	display: block;
	color: white;
`;

return(
	<Button primary>Primary Button</Button> // Green button
);

Theming of styled-components is achieved using a <ThemeProvider> component wrapper which takes a theme property. The theme can provide variables and rules to be used within the component.

import styled from 'styled-components'

const Button = styled.button`
	background: ${props => props.theme.buttonBackground};
	display: block;
	color: white;
`;

const theme = {
	buttonBackground: 'green'
};

return(
	<ThemeProvider theme={theme}>
		<Button primary>Primary Button</Button> // Green button
	</ThemeProvider>
);

jsxstyle

jsxstyle aims to create display components that can wrap components. These display components can take the form of block, inlineBlock, flex and other typical values for the CSS display property. Each display component accepts properties that represent CSS attributes, which are in turn used to style their contents. The built code inserts stylesheets into the DOM with unique CSS class names. This avoids style leakage and enables rules to be reused between components that share styles.

import { Block, Inline } from 'jsxstyle';

return (
	<Block backgroundColor='blue'>
		<Inline color='white'>Blue Button</Inline>
	</Block>
);

Glamorous

PayPal’s Glamorous aims to build upon the ideas of both styled-components and jsxstyle. Glamorous provides a function that receives styles as a JavaScript object as well as providing a collection of components that can be used with property styles. The latter approach allows you to create styled components without having to give them a name.

import glamorous, {Button} from 'glamorous'

const MyButton = glamorous.button({
	backgroundColor: 'blue',
	color: 'white'
});

return (
	<MyButton>Named Button</MyButton>
	<Button backgroundColor='blue'
		color='white'>Anonymous Button</Button>
);

The benefit of not having to provide a name is that this avoids having to add a placeholder HTML element just to provide a reference for a group of items. Inline styles allow you to apply styles directly to a node without naming it in that manner. Glamorous’s approach to allowing similar inline styles (that turn into CSS) to be applied directly to an anonymous node / element provides the same benefit.

Theming in Glamorous is provided via a <ThemeProvider>, much like styled-components, but it also allows a theme to be directly injected into a component. Themes can provide both variables and styles to components.

import glamorous, {ThemeProvider} from 'glamorous'

const myTheme = {
	button: {
		backgroundColor: 'blue',
		color: 'white'
	}
};

const ThemeableButton = glamorous.button((props, theme) => ({
  ...theme.button
}));

return (
	<ThemeProvider theme={myTheme}>
		<ThemeableButton>Themed Button</ThemeableButton>
	</ThemeProvider>
);

CSS in JS (JSS)

CSS in JS uses CSS directly within your JavaScript code. CSS Class names form the top level object keys and each nested object contains the CSS rules. This pattern is used within React and React Native and allows styles to be combined using an array.

// react native example
import {Stylesheet, Button} from 'react-native';

const styles = Stylesheet.create({
	button: {
		background: 'blue',
		color: 'white'
	},
	bold: {
		fontWeight: 'bold'
	}
);

return(
	<Button style={styles.button}>Native Button</Button>
	<Button style={[styles.button, styles.bold]}>Bold Button</Button>
);

In several ways, this reminds us of the early Netscape CSS predecessor, JavaScript StyleSheets (JSSS).

React Themeable

React themeable is an attempt to normalise the styling and theming of React components. The idea is that all third-party components should adopt this approach to level out the inconsistencies of styling and theming. It is highly versatile and allows styles to be written using css-modules, react style, radium and plain CSS.

React Components such as react-autosugest make use of react-themeable and ships with zero styles of its own.

React-themeable provides a component with a single function, themeable, which accepts a theme property and returns a function that may be uses to decorate the relevant nodes. The returned function deals with whether the theme provided is class or style based, and automatically sets up the appropriate attributes.

import themeable from 'react-themeable';

render() {
	const theme = themeable(this.props.theme);

	return (
		<div {...theme(1, 'button')}>
			<span {...theme(2, 'label')}>Themed Button</span>
		</div>
	);
}

The theme for this component must now provide classes or styles for button and label. This approach is highly effective because it gives the component author control over which nodes can receive styles and which cannot, whilst leaving the component user free to use whatever styling approach and technology they see fit.

CSS Modules

CSS modules allows you to write locally scoped class names and animation names using plain CSS. The CSS files are then imported into your JavaScript modules and the classes provided are then used to decorate your DOM nodes. When importing a CSS module, it exports a JSON object with mappings from the specified class names to the localised class names it has generated.

This means that the developer does not need to nest CSS class names in order to achieve style encapsulation. When using CSS modules, you should refrain from id or tag selectors as these cannot be localised by the build process.

When you pair CSS modules with typed-css-modules within a TypeScript project, you are able to get the benefit of Intellisense/auto-completion of CSS class names.

/* from this */
.button .label {
	color: white;
}

/* to this */
.label {
	color: white;
}

/* after build */
.module_name_label_35j2h3g4 {
	color: white;
}
import css from './style.css' // returns map of classnames

return (
	<button className={css.button}>
		<span className={css.label}>My button</span>
		// Span will receive built classname
	</button>
);

PostCSS and PostCSS-cssnext

PostCSS enables you to write cutting edge CSS and down emit your code to a format that the browser can use, much the same way as Babel allows you to write ES6+ code for a wide range of browsers.

PostCSS has a vast library of plugins available to enable various features. PostCSS-cssnext is one of the most useful plugins available, as it allows you to use modern CSS specification features without having to worry about browser support. These include color functions, rule nesting, css-custom-properties and more. PostCSS-cssnext also comes bundled with autoprefixer which alleviates the need to add vendor prefixes to your CSS code and maximises browser support.

:root { 
	--primary-color: 'green';
}
.button {
 	background: var(--primary-color);
	&:hover {
		background: color(var(--primary-color) a(40%));
	}
}

Dojo 2

Dojo 2 aims to provide a typesafe styling and theming approach using the most recent technology available. With a robust widget authoring framework, its very important that the theming system is baked into the widget creation system. Widget theme files are authored as css-modules and compiled using PostCSS and PostCSS-cssnext. This allows widget authors to write CSS files without having to worry about cross-browser compatibility, style leakage and without using a preprocessor language. Each widget has its own style file with a .m.css file extension to mark it as a css-module to the build system. Common module files for icons and app level styles can be imported into your widget file and the classes provided are used to decorate widget nodes. css-custom-properties are used to provide CSS variables to the widgets, and these values are computed at build time so that browser compatibility is maximised. A variables.css file is included with the @dojo/widgets package and can be imported into client CSS files to allow third-party developers to use and extend the Dojo 2 look and feel in their own widgets.

Dojo 2 provides a class level function, classes, which is used to keep track of CSS classes being added and removed from the rendered Virtual DOM and to allow for themes to be used.

import * as css from './styles/myWidget.m.css`;

return v('div', { classes: this.classes(css.root) }, [
	v('span', { classes: this.classes(css.label) })
]);

In order to theme a Dojo 2 widget you must pass it a theme object. The theme object can provide the theme for multiple widgets keyed by their widget name. Dojo 2 widgets from @dojo/widgets are keyed with a dojo- prefix to avoid naming clashes. Themes can target the class names passed to this.classes. For example, to change the span element’s appearance from the above example, a theme should provide an alternative label class.

.label {
	color: red;
}
// import theme file
import * as myWidget from './themes/myTheme/myWidget.m.css`;
import myWidget from './widgets/myWidget';

const theme = {
	myWidget
}

return w(myWidget, { theme } ); //will have a red label

Summary

JavaScript has come a long way and it is great to see that styling, theming and uses of CSS are also progressing quickly. Frameworks like like Dojo 2 and Polymer are making the most of the advances in CSS specifications for css-custom-properties and localisation of class names, which is great for developers who like to write CSS. CSS in JavaScript approaches such as Glamourous and react-styles are proving very popular in the React world due to the auto-completion/Intellisense that they provide within the IDE, not to mention the options they provide for frameworks like react-native. css-modules and TypeScript are a great combination for developers who like their styles to be in CSS files (where they should be!), but still want the Intellisense that CSS in JavaScript approaches provide. There are many options available, to help your providing better styling for your applications, and most importantly, stay away from the !important declaration!