styled-variants
A scalable styled-component theming system that fully leverages JavaScript as a language for styles authoring and theming.
Table of contents
Why another theming library?
At Remine, we not only have multiple global themes (i.e. separate themes for different apps/stakeholders, with light and dark themes for each), but we also have multiple variants: size
(e.g. small, large, etc), state
(e.g. error, warning, etc), and type
(e.g. primary, secondary, tertiary) with more to come. We couldn't find a library in the ecosystem that could support such a large number of variants with minimal code, so we decided to create one.
Most theming systems for styled-components
available today spit out string values (e.g. styled-theming, styled-theme) which can add a code bloat since you still need to assign the theme value to a css property. styled-variants
takes advantage of the first-class object functionality that styled-components
has added in version 3.3.0 to help reduce this bloat and allow for cleaner, scalable theming.
Install
$ npm install --save styled-variants
Usage
See our Buttons example code if you'd rather read code to understand the use cases.
Basic
If we expect to write our HTML like this:
<ThemedButton />
<ThemedButton size="large" />
<ThemedButton size="small" />
DIFFICULT TO READ
You'd normall use the string-based, extremely difficult to read code to dynamically style your components:
export const Button = styled.button`
padding: ${props =>
props.size === "large"
? "1em 1.2em"
: props.size === "small"
? "0.3em 0.7em"
: "0.7em 1em"};
font-size: ${props =>
props.size === "large"
? "1.2rem"
: props.size === "small"
? "0.8rem"
: "1rem"};
`;
Imagine what this would look like if we had even more size options or if "size" affected more css attributes!
EASIER TO READ
With styled-variants, not only can we see very easily what each variants prop value entails, but we can also read what each css value will interpolate to since we no longer have a bunch of conditionals bloating the code:
import styled from "styled-components";
import createTheme from "styled-variants";
const ButtonTheme = createTheme("Button");
const defaultSizeStyles = {
padding: "0.7em 1em",
fontSize: "1rem",
};
const sizeVariant = ButtonTheme.variant("size", {
...defaultSizeStyles,
small: {
padding: "0.3em 0.7em",
fontSize: "0.8rem",
},
large: {
padding: "1em 1.2em",
fontSize: "1.2rem",
},
});
export const ThemedButton = styled.button(sizeVariant);
Passing Props
Just like styled-components
, styled-variants
also supports passing of props via function:
import styled from "styled-components";
import createTheme from "styled-variants";
const ButtonTheme = createTheme("Button");
const defaultTypeStyles = {
color: "white",
border: ({ theme }) => `5px solid ${theme.colors.primary}`,
backgroundColor: ({ theme }) => theme.colors.secondary,
};
export const typeVariant = ButtonTheme.variant("type", {
...defaultTypeStyles,
secondary: {
color: "black",
backgroundColor: ({ theme }) => theme.colors.primary,
borderColor: ({ theme }) => theme.colors.secondary,
},
});
export const ThemedButton = styled.button(typeVariant);
Then we can use styled-components
's ThemeProvider
to inject a theme into the props of our styled-components
:
import { ThemeProvider } from "styled-components";
import { ThemedButton } from "./ThemedButton.js";
const colors = {
primary: "#09d3ac",
secondary: "#282c34",
};
const MyApp = () => {
return (
<ThemeProvider theme={{ colors }}>
<ThemedButton />
<ThemedButton type="secondary" />
</ThemeProvider>
);
};
Boolean variants
If we have separate states (e.g. isDisabled, isActive, isOpen, etc) for each variant, we can easily incorporate those too:
const typeVariant = ButtonTheme.variant("type", {
isDisabled: {
opacity: 0.5,
cursor: "default",
pointerEvents: "none",
},
isActive: {
boxShadow: "0px 0px 1px 1px purple",
},
secondary: {
isDisabled: {
opacity: 0.7,
},
isActive: {
boxShadow: "0px 0px 1px 1px blue",
},
},
});
Then we can pass a prop value for isDisabled
and isActive
:
const MyApp = () => {
const [isDisabled, setIsDisabled] = useState(false);
return (
<ThemeProvider theme={{ colors }}>
<ThemedButton isDisabled={isDisabled} type="secondary" />
<ThemedButton isDisabled={isDisabled} />
<ThemedButton isDisabled={isDisabled} type="secondary" isActive />
</ThemeProvider>
);
};
Combining variants
Thankfully, styled-components
allows for multiple sets of first class objects, so we can do the following to combine our variants:
export const ThemedButton = styled.button(typeVariant, sizeVariant);
Pseudo class support
How to write pseudo classes:
import createTheme from "styled-variants";
const ButtonTheme = createTheme("Button");
const typeVariant = ButtonTheme.variant("type", {
...defaultTypeStyles,
primary: {
color: "green",
"&:hover": {
color: "limegreen",
},
},
secondary: {
color: "black",
"&:focus, &:hover": {
color: "purple",
},
},
});
Extending styled-components
If we have a styled-component already created, we can extend the styles by applying variants:
const sizeVariant = ButtonTheme.variant("size", {
});
const Button = styled.button`
padding: 0.7em 1em;
fontsize: 1rem;
color: blue;
`;
export const ThemedButton = styled(Button)(sizeVariant);
Contributing
Contributions are welcome. Standards have yet to be set but we will set these in the near future.
To see you changes as you make them, an example app has been created. You can run it with:
$ npm run example
License
MIT