Understanding The Styled System
Anvil Engineering
Posted on July 8, 2021
The landscape of CSS has dramatically changed over the years and nowadays you'll find many developers emphatically love CSS-in-JS. First-time CSS-in-JS users often recognize one huge benefit: "Hey, I don't have to toggle between CSS and JS files anymore? Sweet!" While that is an incredible time saver, today I'll be writing about the Styled System, a popular framework for getting the most out of CSS-in-JS and saving you significantly more time.
The Power of CSS-in-JS
If you are already familiar with CSS-in-JS, you can skip this section. I will be recapping its capabilities and highlighting the most important features.
At Anvil, we use styled components in our React applications. From this point on, I will use 'CSS-in-JS' and 'styled components' interchangeably and all examples will be with the styled-components
library.
By bringing CSS to JS we not only save files written, but add all the dynamic capabilities of JavaScript to our styles. During your build step, your styled components will be compiled down into an optimized JavaScript bundle (or bundles, if you have multiple configured) and regular CSS classes directly in the head of your HTML. No CSS stylesheets whatsoever, just optimized CSS ready to go in a style
tag where needed.
Here is an example of how to write a centered, blue box with styled-components:
import styled from 'styled-components'
// definition
const BlueBlox = styled.div`
width: 300px;
height: 300px;
margin: 0 auto;
background: blue;
`
// usage
<BlueBox>I am some text within the blue box!</BlueBox>
This example is literally CSS embedded into JavaScript. Pretty neat, but what if we want multiple boxes of different colors? In regular CSS land, we would have to create a different class for each color… kinda lame to repeat yourself right? Let's use JavaScript and dynamic props to optimize our styled component.
Since the color is going to change, our BlueBox
component doesn't make sense. Let's rename it to ColoredBox
:
import styled from 'styled-components'
// definition
const ColoredBox = styled.div`
width: 300px;
height: 300px;
margin: 0 auto;
background: ${(props) => props.backgroundColor};
`
// usage
<ColoredBox backgroundColor=”blue”>I am the original blue box!</ColoredBox>
<ColoredBox backgroundColor=”red”>I am a new box, my background is red</ColoredBox>
<ColoredBox backgroundColor=”yellow”>Primary colors ftw! Yellow is here as well.</ColoredBox>
This is much better than our old BlueBox
component. By passing a property of backgroundColor
, we are able to easily write different styles of boxes, never worrying about writing new CSS. This power extends to any imaginable CSS out there; you can pass hex values, rgba values, hsl values, etc. to our backgroundColor
prop and it will work. I won't go into detail here in this recap, but you can pass entire style objects with multiple CSS properties dynamically as well. Check out one of my personal favorites from styled-components, the css mixin.
Now that we have the syntax of CSS-in-JS fresh in our minds, let's dig into why we're here: the Styled System.
The Styled System
As the name suggests, the Styled System employs a systematic approach to building components. The systematic approach comes in the form of enabling common style use cases directly in JavaScript. In the above example we saw how to dynamically set the background color using styled-components; imagine a world where you didn't have to write any CSS at all and could still achieve the same result.
That is precisely what the Styled System does: it provides style props that you use when writing JavaScript that take care of all the CSS mess for you. Let's see how to implement our ColoredBox
component using the style props from the Styled System.
import styled from 'styled-components'
import { color, layout, space } from 'styled-system'
// definition
const ColoredBox = styled('div')(
color,
layout,
space,
)
// usage
<ColoredBox bg="yellow" width={300} height={300} m="0 auto">Styled System rocks!</ColoredBox>
Here are the two yellow boxes, for reference:
Same result, but with our style props we get an almost one-line solution. Let's recap what we did:
- imported the appropriate objects from styled-system (
color
,layout
, andspace
) - defined a styled component (
ColoredBox
) using the style props - used our component in the exact way we want to. Repeat this step for any imaginable
ColoredBox
, whenever and however you want!
By using the Styled System, all of the specifics are pushed to the time we actually need to specify them, e.g. when actually using the component.
Abbreviated Style Props
You'll also notice I used bg
for background color and m
for margin in the above example. A secondary benefit of the Styled System are abbreviations like these, plus a few other helpful ones. It isn't the biggest time saver, but typing bg
versus background
or background-color
every time is definitely easier on the fingers. My personal favorites from this convention are horizontal and vertical spacing.
Along with m
for margin, you get p
for padding. Similar shorthands exist for each direction, so for padding you get: pt
for padding-top, pb
for padding-bottom, pl
for padding-left, and pr
for padding-right. Horizontally, you could define pl
and pr
, but why waste time writing both? Use px
, and the Styled System will apply your horizontal padding for you to padding-left and padding-right. Pretty sweet bonus, since there's no native way to do a one liner for just left and right spacing. This applies to all spacing properties, and there is indeed a py
for vertical spacing.
Benefits of The Styled System
By using this framework and its pattern, you unlock a multitude of benefits that it supports. You can read more about the benefits of the Styled System here, but the two we will focus on throughout this blog post are:
- Style props that pick up values from a global theme
- Quickly set responsive font-size, margin, padding, width, and more
Theming
We have seen how the Styled System provides style props for us, allowing dynamic styling. But where should the actual style values reside? The answer is up to you and truly depends on your needs; if you are doing a side project by yourself, starting out with hardcoded values is a perfectly valid option.
Where things get complicated is when you have a team of people building a real product. What shade of red are we using for the logo? What shade of red are we using for a button to indicate a dangerous action? Two very different scenarios, but very easily confused if using hardcoded values.
Styled System solves this problem by providing a way to theme your application. It uses object notation via the System UI Theme Specification. This theme object falls under an age-old information architecture adage: Single Source of Truth. By adhering to a theme, you are enforcing consistency across all components and pages of your application, while also enabling easy swapping of values. Think of all the 'dark mode' web apps and sites out there; each has some notion of theming, in which style values are swapped based on what theme is chosen. For each of those sites, the components themselves don't change, but the style values do.
The System UI Theme Specification works primarily on the idea of scales. Spacing and typography are defined using arrays in which each element is incremented by the same amount. For open-ended values like colors, an object is used to define values. And lastly, the keys in our theme object correspond to the pluralized, camel-cased form of the underlying CSS property, e.g. color
will be colors
, font-size
will be fontSizes
, and so on. The only exception is the space
key, which represents all space properties, including all margin and padding variants.
With all that being said, everything is configurable. Besides the naming convention, your scales can be implemented however you want or you don't have to include a scale at all. Depending on how you actually use the theme, you might not even need to follow the naming conventions laid out in this article and on the System UI Theme Specification itself. But be warned, you miss lots of built-in benefits if you stray away from the convention!
Here is an example of a complete theme:
const black = '#222222'
const blue = '#00C0F2'
const red = '#FF5C5C'
const yellow = '#F8BF95'
const colors = {
blacks: {
5: '#fafafa',
10: '#d8d8d8',
20: '#bbb',
30: '#999',
40: '#7a7a7a',
50: '#525252',
60: '#3d3d3d',
90: black,
},
blues: {
10: '#F4F6F9',
20: '#def3f7',
30: blue,
40: '#00ABD7',
},
reds: {
30: red,
},
yellows: {
30: yellow,
},
black,
blue,
red,
yellow,
}
colors.textColor = colors.black
const space = [0, 5, 10, 15, 20, 25, 30, 35, 40]
const fontSizes = [12, 14, 16, 18]
const fontWeights = {
light: 200,
thin: 300,
normal: 400,
bold: 700,
black: 900,
}
const fontStack = 'Comic Sans, -apple-system, BlinkMacSystemFont, sans-serif'
const fontFamilies = {
body: fontStack,
heading: fontStack,
}
export default {
colors,
space,
fontSizes,
fontWeights,
fontFamilies,
}
ThemeProviders
Of the many ways to use a theme object, using a ThemeProvider
(if one is provided by your CSS-in-JS library) is the best way to go. It leverages the React context API to pass down your theme values to all components of your application.
Taking the example from Styled System itself:
// in App.js
import React from 'react'
import { ThemeProvider } from 'styled-components'
import theme from './theme'
const App = props => (
<ThemeProvider theme={theme}>{/* application elements */}</ThemeProvider>
)
export default App
// in any other component, this will pick up 'black' and 'blue' from our theme!
<Box color="black" bg="blue">
Blue Box
</Box>
Element Variants
Our last stop for theming an application is adding element variants to your theme. There currently exist 3 included element variants: buttons
, textStyles
, and colorStyles
. While we don't use element variants at Anvil, they are pretty sweet upgrades to base styles in your theme and can seriously help your style writing productivity.
Element variants work by grouping any style you want to apply and assigning it to a key. Below is an example for buttons
; if you have used CSS frameworks like Bootstrap before, these variants should feel familiar.
// theme.js
const buttons = {
primary: {
color: colors.white,
backgroundColor: colors.blue,
},
secondary: {
color: colors.white,
backgroundColor: colors.green,
},
danger: {
color: colors.white,
backgroundColor: colors.red,
},
}
export default {
buttons,
}
// using a button variant
<Button variant='primary' />
Responsive Styling
“Quickly set responsive font-size, margin, padding, width, and more” sounds a bit odd and hard to quantify. Responsive Design revolutionized the web, but how do you set that up quicker than a few media queries and use the max-width
property?
The answer is by combining the previous section (theming your application) with defined breakpoints for your entire application.
Similar to theming, defining breakpoints for your application provides consistency and better maintainability. I won't go into the nuances of responsive design and what are good breakpoints to set, but a 'breakpoint' is where your application's style changes based on the viewport width. For example, a widely accepted breakpoint is 769px for tablets; anything above that is considered wider than a tablet, so the next breakpoint's styles would apply.
Writing media queries for each page or each component of your application gets tiring, fast. The Styled System makes it a breeze by passing your breakpoints to a ThemeProvider
, which now gives all components created with Styled System the ability to take arrays as values instead of their normal values. Each value in the array corresponds to the value that will be applied at each one of your specified breakpoints, which is incredibly easy to write compared to media queries.
For example:
// passed to ThemeProvider
const breakpoints = [600, 800, 1000];
// using breakpoints
<Box
width={[
1, // 100% below the smallest breakpoint
1/2, // 50% from the next breakpoint and up
1/4 // 25% from the next breakpoint and up
]}
/>
// responsive font size
<Box fontSize={[ 1, 2, 3, 4 ]} />
// responsive margin
<Box m={[ 1, 2, 3, 4 ]} />
// responsive padding
<Box p={[ 1, 2, 3, 4 ]} />
Each of these Box
s will have media queries set up to do the responsive design for you, using a mobile-first approach. For the last three Box
s, there are 4 values despite 3 breakpoints; in this case, the last value is for any viewport width over the last breakpoint of 1000px.
Reflexbox
At Anvil, we use responsive styling via the Styled System extensively. We use a component provider called Reflexbox by Rebass, which is an entire suite of prebuilt primitive components to use. Instead of reinventing the wheel, we utilize these components from Reflexbox to build our components with ease.
I recommend you read the docs for Reflexbox, but fun fact--you already know how it works! There are 2 components built with the Styled System, Box
and Flex
. Box
takes the layout
, space
, color
, and typography
props, while Flex
has all of those plus flexbox
props. Both components support responsive styling as well 🤘🏼
Putting it all together
In this blog post we covered the Styled System in-depth. If you are developing using the Styled System, you likely do not need to know such a granular level of detail, but like all things in software development, you tend to grok the technology the more you dig into it.
We saw how CSS-in-JS enables much more than less source code files, but truly rapid component and style development. We also saw how theming and responsive styling streamline app development and provide consistency throughout entire applications, while enabling easy maintainability for style values.
I'll leave you with one tidbit on the limitations of the Styled System: not all CSS values are supported. If you look into the color
prop source code, you'll find this:
import { system } from '@styled-system/core'
const config = {
color: {
property: 'color',
scale: 'colors',
},
backgroundColor: {
property: 'backgroundColor',
scale: 'colors',
},
opacity: true,
}
config.bg = config.backgroundColor
export const color = system(config)
export default color
This looks well and good, but config.bg
raises a question or two… We can use bg
on a component to set the background-color
, but how about background-image
? The CSS background
property is shorthand for 8 other properties (yes, 8!), yet only 1 is truly supported here. These are common things we can do in CSS, but the way this is implemented we can't do with the Styled System.
Luckily for you and me, we can define our own custom props. Quite an amazing system, if we can extend it like this!
I hope you enjoyed this blog post, and if you write your own custom props we'd love to hear about it. Send us a message at developers@useanvil.com, and we'll publish all the ones we get. Happy coding 🤘🏼
Posted on July 8, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.