Stitches is an excellent library if you prefer to be as close as writing CSS with the bonus of having a solid JS API. For this blog, let's create a React application with Stitches to see how the styling library would integrate and influence how we code our interactivities.
We will be creating a straightforward app with the following functionalities.
- Show a "Karma Chameleon" in the center.
- Three buttons with the labels
Light
,Dark
andFunky
- Clicking any buttons would update the background and text color of "Karma Chameleon" to their corresponding theme.
TLDR; a theme toggle application.
For simplicity's sake, let's use create-react-app (CRA) for this one. I won't be changing anything out of the ordinary. I will remove all the unnecessary artifacts like CSS and other setups. After the fact, I should only have the KarmaChameleon.js
in the components folder and the index.js
that injects the only defined component.
To easily create your app with CRA, just run the command below:
npx create-react-app karma-chameleon

Next stop, let's install Stitches! We will be using the react-based library instead of the framework-agnostic core. I like yarn
so I'll use yarn
.
yarn add @stitches/react
My approach with any design framework is heavily using design tokens. Tokens define the values of our design system. Luckily, Stitches makes that easy for us. But, before you see that in action, let's define the bread and butter of Stitches, the configuration file.
Let's only define the Light theme, aka the default theme for now. The other values are personal preferences. Feel free to change them depending on your app's design context. Read through the documentation for more information on the different keys I used and any others you can define. I depicted my global CSS preferences as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
// create stitches.js in src import { createStitches } from "@stitches/react"; const stitches = createStitches({ media: { bp1: "(min-width: 320px)", bp2: "(min-width: 768px)", bp3: "(min-width: 1200px)", }, theme: { colors: { // generated from coolors.co aliceBlue: "#F4FAFF", davyGrey: "#535657", cadetBlue: "#4F646F", platinum: "#DEE7E7", darkKhaki: "#B5BA72", darkSlateBlue: "#4F359B", // elements bg: "$aliceBlue", fg: "$davyGrey", }, space: { xxs: "0.422rem", xs: "0.563rem", sm: "0.75rem", rg: "1rem", md: "1.33rem", lg: "1.77rem", xl: "2.369rem", xxl: "3.157rem", }, fontSizes: { xxs: "0.422rem", xs: "0.563rem", sm: "0.75rem", rg: "1rem", md: "1.33rem", lg: "1.77rem", xl: "2.369rem", xxl: "3.157rem", }, }, }) const injectGlobalStyles = stitches.globalCss({ "*": { boxSizing: "border-box", fontFamily: "Hammersmith One" }, "*:after": { boxSizing: "border-box", fontFamily: "Hammersmith One" }, "*:before": { boxSizing: "border-box", fontFamily: "Hammersmith One" }, body: { margin: 0, padding: 0 }, h1: { margin: 0 }, }) injectGlobalStyles() export default stitches
Given the configuration above, we may now implement the initial view of the React application with the default theme! The object returned from the createStitches
call contains different APIs that would help style your components. We will gradually touch some of them.
See tokens in action below! For example, we used the $bg
token to get the value of the variable bg
under the default theme. That's right! Stitches is smart enough to do the corresponding substitution. Not to mention, since it's typed, auto-suggestion is active!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
// KarmaChameleon.js import stitches from "./stitches"; const { styled } = stitches; const Container = styled("div", { alignItems: "center", backgroundColor: "$bg", color: "$fg", display: "flex", flexDirection: "column", justifyContent: "center", minHeight: "100vh", width: "100vw", }); const ButtonContainer = styled("div", { display: "flex", fontSize: "$xxl", marginTop: "$lg", maxWidth: "1200px", "> button": { "+ button": { marginLeft: "$lg", } } }); const Button = styled("button", { backgroundColor: "$bg", border: "2px solid", borderColor: "$fg", borderRadius: "25px", boxShadow: "none", color: "$fg", fontSize: "$rg", padding: "$rg $lg", "&:hover": { cursor: "pointer", }, }); const App = () => { return ( <Container> <h1>Karma Chameleon</h1> <ButtonContainer> <Button>Light</Button> <Button>Dark</Button> <Button>Funky</Button> </ButtonContainer> </Container> ); } export default App;
To summarize, we used styled
to generate a React component with the following defined styles. We used design tokens in some of the values to use the value specified in our configuration, which will be necessary for our theme overrides and, in general, a good practice for consistency. The globalCss
API creates a function we can call to define the app's global CSS. Look at the preview below.

Now, we need to define the other themes. The createTheme
API is our hero for this. We will use a state to track the current theme, enabling us to change it through the corresponding button clicks. It follows we need to refactor a little bit to allow value overrides through design tokens. See it in action below!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
// stitches.js export const darkTheme = stitches.createTheme({ colors: { bg: "$darkJungleGreen", fg: "$fluorescentBlue", } }); export const funkyTheme = stitches.createTheme({ colors: { bg: "$darkKhaki", fg: "$darkSlateBlue", } }); // KarmaChameleon.js import stitches, { darkTheme, funkyTheme } from "./stitches"; // ... other stitches stuff from earlier const themeMap = { light: null, dark: darkTheme, funky: funkyTheme, } const App = () => { const [theme, setTheme] = useState('light') return ( <Container className={themeMap[theme]}> <h1>Karma Chameleon</h1> <ButtonContainer> <Button onClick={() => setTheme('light')}>Light</Button> <Button onClick={() => setTheme('dark')}>Dark</Button> <Button onClick={() => setTheme('funky')}>Funky</Button> </ButtonContainer> </Container> ); } export default App;
And..... viola!

Time for finishing touches! We want responsiveness in our application. Flashback on the configuration, we defined three breakpoints. Keeping that in mind, we can use the tokens to create media queries, the Stitches way. The @bp2
token is (min-width: 768px)
in our configuration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// only the buttons need to adjust // mobile first approach const ButtonContainer = styled("div", { display: "flex", fontSize: "$xxl", marginTop: "$lg", maxWidth: "1200px", flexDirection: "column", "@bp2": { flexDirection: "row", }, "> button": { "+ button": { marginTop: "$lg", "@bp2": { marginTop: 0, marginLeft: "$lg", }, }, }, })
Having a screen size less than 768px
will result to the view below.

Of course, we want the transition of the background and foregrounds to be smooth. We can also add simple animations with the keyframes
API of Stitches for those fabulous fade-ins. You may use the css
prop to override styles. In this case, I needed to add individual animation delays to produce the effect that I wanted for the buttons.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
// KarmaChameleon.js const { styled, keyframes } = stitches; const goIn = keyframes({ "0%": { opacity: 0, transform: "translateY(10px)", }, "100%": { opacity: 1, transform: "translateY(0px)", visibility: "visible", } }); const Container = styled("div", { ... other styles from before, transition: "background-color ease-in 0.5s", "> h1": { animation: `${goIn} ease-in 0.5s`, }, }); // ... other stitches stuff const Button = styled("button", { animation: `${goIn} ease-in 0.5s`, animationFillMode: "forwards", ...other styles from before, }); const App = () => { const [theme, setTheme] = useState('light') return ( <Container className={themeMap[theme]}> <h1>Karma Chameleon</h1> <ButtonContainer> <Button css={{animationDelay: "0.25s"}} onClick={() => setTheme('light')} > Light </Button> <Button css={{animationDelay: "0.5s"}} onClick={() => setTheme('dark')} > Dark </Button> <Button css={{animationDelay: "0.75s"}} onClick={() => setTheme('funky')} > Funky </Button> </ButtonContainer> </Container> ); } export default App;
Time for some magic (> o_o)>/*

Another minor nit is an indicator of which theme is currently present. We will use the variants
API to swap the foreground and background when the button's theme is currently selected.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
const Button = styled("button", { ... other styles from before, variants: { selected: { true: { color: "$bg", backgroundColor: "$fg", }, }, }, }); const App = () => { const [theme, setTheme] = useState('light') return ( <Container className={themeMap[theme]}> <h1>Karma Chameleon</h1> <ButtonContainer> <Button css={{animationDelay: "0.25s"}} selected={theme === 'light'} onClick={() => setTheme('light')} > Light </Button> <Button css={{animationDelay: "0.5s"}} selected={theme === 'dark'} onClick={() => setTheme('dark')} > Dark </Button> <Button css={{animationDelay: "0.75s"}} selected={theme === 'funky'} onClick={() => setTheme('funky')} > Funky </Button> </ButtonContainer> </Container> ); } export default App;
Then, we are done!

Plus ultra! The final code is on this repository or check out this website. Feel free to do whatever you want with it.
There is more to explore with Stitches. I've barely scratched the surface. To name a few, you can define utilities in the configuration to extend the available keys on our styles, aka making your own higher order CSS rules, and support SSR.
To summarize, Stitches has the following advantages:
- faster benchmarking vs. emotion and styled-components
- robust JS API (as you have noticed above)
- supports SSR
- built with React in mind.
- Typed. AUTOCOMPLETE IS EFFIN AWESOME! It also checks type integrity with variants and utilities, which I appreciate.
It's not all unicorns and rainbows, to be honest. I noticed some hit to the dev experience, primarily if you're used to just using CSS. I listed below some of the downsides of using this library.
- It's not precisely CSS that can be a good or bad thing.
- You use CSS syntax on the values, but camel-case converted CSS strings on keys.
- It might be too React centric, as the core library uses class names that could clash with a library's/framework's paradigm.
- Non-modern browsers won't run this. (but what styling JS library does? haha)
- Custom props are limited to variants, and you can't do prop-based logic without extra overhead, unlike in
styled-components
where you can access props and do whatever you want.
I love this library. I already used it with this blog and other misc projects. TLDR; React + Stitches = Awesome.