Architecting a composable style API with Figma and Stitches
Maintaining a complex design system and style guide across brand, product, and engineering teams is complicated and requires close collaboration between all parties to deliver a cohesive and scalable product. Tooling that reduces the back-and-forth between these parties can alleviate stress around updating and expanding areas of the system and brand.
Figma already accelerates this process with robust styling and variable definitions, but translating those styles into code can sometimes be verbose and lack the DRY (don't repeat yourself) concept of development.
Though still in beta, Stitches aims to reduce the complexity of translating a design system and brand primitives into a scalable application and component library.
Let's walk through how simple it is to map a set of brand primitives defined in Figma, to a styled system within an application. I'll be using the design system I've been building for Downbeat Academy as an example, but the strategy and implementation are simple such that these patterns can be applied to almost any design system and application. Access the forked Figma project here.
This isn't meant to be an exhaustive tutorial on how to create a design system in Figma or set up a Stitches config in a React app, more of a high-level overview of how these tools work together to better the developer experience and encourage more streamlined communication between teams.
Note: Downbeat Academy is going through some massive updates currently which are not reflected in the production environment. Stay tuned for a rollout of some great new educational features!
Why Stitches?
Styling within a single page application is a bit of a hot topic with as many solutions and tooling options as there are blog posts stating why you shouldn't use anything except X. Aside from an excellent developer experience, Stitches touts first-in-class SSR (server-side rendering) support, excellent performance, small bundle size, and a framework-agnostic core library (though for this example we'll be using React).
These are all excellent value propositions through the lens of a developer, but equally important are the similarities in semantics and architecture between Stitches and Figma. The process and strategy for defining brand primitives as styles within Figma and designing a component library on top are mirrored almost exactly in a Stitches configuration and React component library.
Check out the key features of Stitches.
Compared to Styled Components
Something to note, I'm coming from a background in Styled Components and value styling that is scoped at the component level. I generally use a global style sheet with the createGlobalStyle
helper with CSS variables to define colors, fonts, and other global variables.
Mapping styles and brand primitives
The most foundational elements within a Figma design system are styles, documenting the color palette, type stack, box shadows, and other low-level primitives. Consistent organization of these styles is essential to building a cohesive application that scales as new features and products are introduced.
Using the Downbeat Academy color palette as an example (view here), each color is organized using a semantic naming convention into 10 different tints and shades generated using ColorBox from the base color. In the case of Downbeat Academy, each color corresponds with a different fruit. Thus, using the naming convention and numerical breakpoints for tint and shade, the Figma color style for the primary color maps to Passion Fruit / 500
.
This is an image
This has multiple benefits, most notably assigning human-readable values to colors and extending the base color in both directions, allowing for an abundance of color pairings to solve accessibility issues while maintaining brand flexibility.
Defining a Stitches theme
When setting up a project you intend to use Stitches with, all of your styles and brand primitives are encapsulated in a comprehensive config file containing a theme, media breakpoints, and utilities to handle custom logic in your styling. We're mainly going to focus on the theme created with Stitches in this example.
This config file acts as a single source of truth for all of your brand primitives and generates CSS variables from each token, creating a flexible API for your project.
import { createCss } from '@stitches/react'
export const { styled, global, getCssString } = createCss({
theme: {
fonts: {
apercu: 'Apercu, Helvetica, Arial, sans-serif',
publicoText: 'Publico Text, Georga, Times, serif',
mono: 'Apercu Mono, Lucida Console, monospace'
},
colors: {
passionFruit100: 'hsl(220, 65%, 92%)',
passionFruit200: 'hsl(224, 67%, 82%)',
passionFruit300: 'hsl(228, 74%, 75%)',
passionFruit400: 'hsl(230, 80%, 69%)',
passionFruit500: 'hsl(234, 83%, 64%)',
passionFruit600: 'hsl(235, 58%, 53%)',
passionFruit700: 'hsl(235, 54%, 42%)',
passionFruit800: 'hsl(235, 56%, 33%)',
passionFruit900: 'hsl(235, 56%, 23%)',
passionFruit1000: 'hsl(234, 56%, 14%)',
},
fontSizes: {
displayBase: '1.125rem',
displayLarge: '1.5rem',
displaySmall: '0.875rem',
displayExtraSmall: '0.75rem',
displayMega: '4rem',
displayH1: '3rem',
displayH2: '2.125rem',
displayH3: '1.75rem',
displayH4: '1.5rem',
displayH5: '1.25rem',
displayH6: '0.875rem',
interfaceBase: '1rem',
interfaceLarge: '1.25rem',
interfaceSmall: '0.875rem',
interfaceExtraSmall: '0.75rem',
interfaceH1: '2.5rem',
interfaceH2: '2rem',
interfaceH3: '1.75rem',
interfaceH4: '1.4375rem',
interfaceH5: '1.1875rem',
interfaceH6: '1rem',
},
},
});
Example stitches.config.ts
Not only does this make translating Figma styles into a composable API incredibly easy, but it cuts down significantly on the back and forth between design and engineering when all of the styles are easily accessible within Figma. Mirroring the Figma style hierarchy and the Stitches configuration in this fashion leads to more consistency and simplified communication between brand leaders and engineering.
Theme tokens in context
Defining styles in Figma and mapping them to Stitches theme tokens is one thing, but let's look at how these tokens are applied in the context of a button.
// Importing your Stitches configuration
import { styled } from 'styles/stitches.config'
const StyledButton = styled(`button`, {
// Referencing font and color tokens within a button component
fontFamily: '$apercu',
color: '$accent100',
backgroundColor: '$passionFruit500',
})
Styled Button Component
Once a Stitches config file has been defined, the style function is available throughout the entire application giving you access to tokens and anything else defined in the config.
Token mapping
By default Stitches tokens are mapped to a predefined set of CSS properties; tokens within the color category of the theme are mapped to CSS properties that accept color values, background-color
, color
, border-color
, etc. This makes composing components easier with VS Code Intellisense and TypeScript, however, your tokens are still accessible outside of mapped properties by prefixing the token value with the theme category.
// Will return the passionFruit500 color
color: '$passionFruit500',
// Will also return the passionFruit500 color
color: '$color$passionFruit500',
// Will return a syntax error
color: '$apercu',
// Will return a font size for a CSS color
color: '$fonts$apercu',
Stitches property mapping
Stitches has excellent documentation, read more about how properties are mapped and theme tokens are created here.
Composing components & variants
Now that we've defined styles and tokens for our brand primitives, we can move up in the atomic hierarchy and start building components with multiple variants. Doing so in Figma has been pretty painless since variants were introduced, we're going to continue our example using a button component from the Downbeat Academy design system here.
This is an image
Variants can be made up of essentially whatever properties you want, for this example we're going to focus on button type
(different types of buttons used based on the context) and button state
(different interactions the user has with the button).
We'll define button types
as:
- Primary
- Secondary
- Tertiary
- Ghost
We'll define button states
as:
- Rest
- Hover
- Active
- Focus
Obviously, this example could be expanded to include different error and exception handling, sizes, icons, etc, but for the sake of this post, we'll narrow the focus to these variants and how to combine them.
Defining a variant in Styled Components
Having used Styled Components extensively in the last few years, I've come across a pattern that works well for me when defining variants, but let's analyze the shortcomings of this method.
const StyledButton = styled.button`
font-family: Helvetica, Arial, sans-serif;
font-size: 1.25rem;
padding: 1rem 1.5rem;
// Change the color based on the type prop
color: ${props =>
props.type === 'primary' ? 'white'
: 'secondary' ? 'rebeccapurple'
: 'white'
};
// Change the background color based on the type prop
background: ${props =>
props.type === 'primary' ? 'rebeccapurple'
: 'secondary' ? 'white'
: 'rebeccapurple'
}
`
Style Components Button
Not only is this syntax at odds with how variants are composed in Figma, but it's also incredibly verbose and relies on a ternary operator to check if a parameter is true or false. This isn't very performant and lacks the semantics that we would expect with a modern library.
Let's rewrite that example using Stitches.
const StyledButton = styled('button', {
// Base styling for all buttons
fontFamily: '$apercu',
fontSize: '$interfaceBase',
variants: {
type: {
primary: {
color: '$accent100',
backgroundColor: '$passionFruit500',
'&:hover': {
backgroundColor: '$passionFruit600',
},
'&:focus': {
borderColor: '$passionFruit500',
},
'&:active': {
backgroundColor: '$passionFruit700',
},
},
secondary: {
color: '$accent100',
backgroundColor: '$blackberry900',
'&:hover': {
backgroundColor: '$blackberry700',
},
'&:focus': {
borderColor: '$blackberry600',
},
'&:active': {
backgroundColor: '$blackberry700',
},
},
// Repeat for tertiary and ghost
}
},
// Set the default variant
defaultVarints: {
type: 'primary',
}
}
Stitches Button with multiple type variants
Here we define a base styling that applies to all buttons, then outline variant-specific styling, using native browser state management.
Using the StyledButton
in the application is super simple.
import StyledButton from 'components/button'
export const App = () => {
return (
<>
<Button type='primary'>This is a primary button</Button>
<Button type='secondary'>This is a secondary button</Button>
</>
)
}
Button in context
Not only is this syntax straightforward and easy for a non-developer to understand, but it also makes explicit use of the theme tokens we already defined, enabling scalability and making documentation much easier.
Downsides
If you're coming from Styled Components, the Stitches syntax requires a bit of a brain shift. Stitches uses JSON syntax for almost everything which means writing CSS properties using camelCase rather than traditional CSS property names. However, this is a pretty friendly syntax to work in and doesn't take a lot of adjustment.
Stitches is also still technically in beta, so things might change rapidly if you're working on a project. I say technically because the Stitches team is already using it in multiple production environments. There also isn't currently much documentation surrounding TypeScript, but I have heard rumblings in the Stitches community about plans for supporting TypeScript and adding to their documentation.
Wrapping up
While styling within a SPA is subjective in a lot of circumstances, any tooling that eases the lines of communication between designers and engineers helps. While this setup might not be for everyone, I would imagine that this type of style API design will become commonplace in the near future.
Try Stitches out for yourself and let me know your thoughts!