In a recent ReactiveConf session, Scott Tolinski defended the thesis that developers, due to recent additions to the CSS language, may not need to use a full-fledged CSS framework. Tolinski further demonstrated how developers who do not need to support IE11 can leverage CSS variables to implement a custom design system with characteristically less overhead than a framework.
CSS variables allow developers to express the dynamic relations between CSS properties that result in a target layout. CSS variables, also called CSS custom properties, are declared by prefixing their names with --
(e.g. --background
). A CSS variable has a value that can be used in other CSS declarations using the var()
function. The variable can be updated either via CSS or JavaScript. When such an update occurs, all dependent variables will be updated reactively. CSS variables are scoped to the element(s) they are declared on and participate in the cascade.
Having explicit, named, scoped variables, and user-defined functional computations (var()
) allows developers to express custom algorithms closer to how they would in a Turing-complete language like JavaScript. While CSS was originally designed to describe static data (markup language) rather than computations (programming language), the necessity to economically describe dynamically changing layouts has brought CSS ever closer to a regular programming language, while staying true to its declarative roots.
Tolinski gave a concrete demo taken from the complete redesign of a tutorial website in which the user could pick one of six themes, resulting in the user interface changing appearance accordingly. The JavaScript required to implement the functionality consisted only of changing a single class. For instance, the oled
theme was associated to the oled-mode
class with the following CSS variables configured:
.oled-mode {
--bg-color: var (--darkPurp)
--sheetHover: var (--black-20)
--sheetTextColor: var (--white)
...
}
Note how in the previous code variables (e.g. bg-color) are computed from other variables (e.g. darkPurp
), creating a list of explicit dependencies. The corresponding CSS code is more maintainable: it is both easier to change, and easier to circumscribe what needs to change. Here, modifying the dark purple value will automatically be reflected everywhere necessary. Modifying the black color will for sure not modify the background color.
Tolinski continued by illustrating how CSS variables can help craft entire design systems. For the sake of the talk, Tolinski reduced a design system to key components that make the design unique: color, type, spacing, characters, elevation, and elements (e.g. cards or accordions).
Palettes can be encoded in CSS variables. Existing tools can for instance generate adaptive, accessible color palettes from a few basic colors, and contrast targets. After defining the colors, Tolinski proceeded with what he termed colors’ intentions (e.g. --bgColor
or lineColor
).
Typography-wise, Tolinski recommended leveraging existing tools to visualize fonts and their scale ratio. An example of code auto-generated for copy-pasting purposes by type-scale.com
is as follows:
@import url('https://fonts.googleapis.com/css?family=Poppins:400|Poppins:400');
html {font-size: 100%;} /*16px*/
body {
background-color: white;
font-family: 'Poppins', sans-serif;
font-weight: 400;
line-height: 1.65;
color: #333;
}
p {margin-bottom: 1.15rem;}
h1, h2, h3, h4, h5 {
margin: 2.75rem 0 1.05rem;
font-family: 'Poppins', sans-serif;
font-weight: 400;
line-height: 1.15;
}
h1 {
margin-top: 0;
font-size: 3.052em;
}
h2 {font-size: 2.441em;}
h3 {font-size: 1.953em;}
h4 {font-size: 1.563em;}
h5 {font-size: 1.25em;}
small, .text_small {font-size: 0.8em;}
Tolinski then replaces the generated values by CSS variables:
--baseFontSize: 1rem
--baseNavSize: 0.64rem
...
--xtra-big-ass-heading: 3.052rem
--xtra-heading: 2.441rem
...
--heading-one: 1.953rem
...
--heading-four: var(--baseFontSize)
--heading-five: var(--smallFontSize)
...
--headingFont: 'Poppins', sans-serif;
--bodyFont: 'Raleway', sans-serif;
...
Extra typography resources include modularscale, Figma typography recommendations, and vertical-rhythm-reset.
Tolinski recommends avoiding the traps of margin collapsing by using either margin-left
or margin-right
(margin-up
or margin-down
) but not both.
The next component of a design system, elements, are styled components, e.g. <Card />
or <div class="card">
. An element should be simple, obvious, hard-to-break and extendable. Tolinski here recommends techniques similar to those advocated by Heydon Pickering, techniques by which an enclosing component imposes layout properties to its children in a generic way. The generic Stack layout primitive for instance injects margin between elements via their common parent as follows:
.stack > * + * {
margin-top: 1.5rem;
}
The linked article goes into detail on the advantages of such a formulation and introduces other generic layouts which can be achieved with similar wildcard techniques (e.g. the Sidebar, the Cover, and more).
Tolinski concluded with the following advice:
Frameworks require overhead of both kb and onboarding. […] Write only what you need, use variables as the backbone.
Always use variables for any color, typography, spacing, and your entire site can be updated or configured in one fell swoop. Don’t worry about creating unique components if they are all using custom properties.
ReactiveConf is a yearly conference targeted at developers with talks addressing the latest technologies and trends in software development. ReactiveConf 2020 has been postponed to the first trimester of 2021 due to Covid-19. In the meantime, talks are broadcasted online on a regular basis. Tolinski’s talk is available online and contains many more examples and technical details.