(Read the official Stitches docs on variants)
Variants are a neat feature of Stitches which allow your styled components to react to props.
Each variant has a single 'name', and then one or more possible 'values'. Each value maps to a set of styles, which are conditionally applied when the correct prop is supplied.
const Button = styled("button", {
typography: "ui",
px: "$2",
py: "$1",
backgroundColor: "black",
color: "white",
"&:disabled": {
opacity: 0.5,
cursor: "not-allowed",
},
"&:hover": {
backgroundColor: "grey",
},
variants: {
color: {
blue: {
backgroundColor: "blue",
"&:hover": {
backgroundColor: "cyan",
},
},
yellow: {
backgroundColor: "yellow",
color: "black",
"&:hover": {
backgroundColor: "orange",
},
},
},
large: {
true: {
typography: "uiLarge",
px: "$3",
py: "$2",
},
},
},
})
Above, we've defined a simple Button component, which now has two props (in addition to the regular HTMLButton props) ā color and large. It can be used like so:
<Button>Regular button</Button>
<Button disabled>Disabled button</Button>
<Button color="blue">Blue button</Button>
<Button color="blue" large>Large blue button</Button>
There are some problems with the above component! Let's take a look at variables next, then see if we can use them to
make our Button component even nicer, and more maintainable
Technically "locally scoped tokens", but these are actually just regular CSS variables which cascade down to child elements. Stitches provides a shorthand for working with CSS variables.
So instead of var(--myVariable) you can just do $$myVariable
(Read the official docs on locally scoped tokens here)
Side note, $$myVariable actually gets turned into ---myVariable, with THREE dashes instead of the usual 2.
Variables can be declared globally, or within components. Either way, those variables will be accessible by any child elements.
Using variables globally can look something like this:
// theme.css.tsx
export const frontendGlobalStyles = globalCss({
body: {
$$headerHeight: "40px",
"@laptop": {
$$headerHeight: "80px",
},
},
})
// components/site/SiteHeader.tsx
export const Header = styled("div", {
position: "fixed",
left: 0,
top: 0,
right: 0,
height: "$$headerHeight",
})
// views/_app.tsx
export const ContentWrapper = styled("div", {
paddingTop: "$$headerHeight",
})
In the above example, we only need to update the mobile and desktop header size in a single file, but using it multiple throughout our application.
Another example where variables are useful, is for simply reducing duplication of values:
const IconWrapper = styled("div", {
$$size: "24px",
width: "$$size",
height: "$$size",
})
And even better, we can make them responsive!
const IconWrapper = styled("div", {
$$size: "18px",
width: "$$size",
height: "$$size",
"@laptop": {
$$size: "24px",
},
})
And even better, we can combine them with variants as well, and chain them!
const IconWrapper = styled("div", {
$$mobileSize: "18px",
$$desktopSize: "24px",
$$size: "$$mobileSize",
width: "$$size",
height: "$$size",
"@laptop": {
$$size: "$$desktopSize",
},
variants: {
large: {
true: {
$$mobileSize: "32px",
$$desktopSize: "48px",
},
},
},
})
Above, we're literally declaring the width and height once, and we're also only declaring each pixel size once. This is the ultimate in code efficiency.
Let's make our button component from the top of the page even better, using variables.
const Button = styled("button", {
$$bg: "black",
$$fg: "white",
$$bgHover: "$$bg",
$$fgHover: "$$fg",
px: "$2",
py: "$1",
typography: "ui",
backgroundColor: "$$bg",
color: "$$fg",
"&:disabled": {
opacity: 0.5,
cursor: "not-allowed",
},
"&:hover": {
backgroundColor: "$$bgHover",
color: "$$fgHover",
},
variants: {
color: {
blue: {
$$bg: "blue",
$$fg: "white",
$$bgHover: "cyan",
$$fgHover: "white",
},
yellow: {
$$bg: "yellow",
$$fg: "black",
$$bgHover: "orange",
$$fgHover: "$$color",
},
},
large: {
true: {
typography: "uiLarge",
px: "$3",
py: "$2",
},
},
},
})
š Our colour variants are now drive purely by variable, making extending this component with more colour schemes even easier ā we can just override those variables in future variants. Not only that, but our implementation for how this component works is now only defined once, and our colour variants just tweak the parameters. This makes the component more maintainable as our application grows, since we can continue to change the implementation without having to update ever variant ā introducing child elements, pseudoselectors, more styling and complexity.
We could also potentially use the $fg variant for icons, or add new CSS properties like a border or boxShadow which also draw from these existing variables.