Table Of Contents
<img height="70px" width="70px" src="https://octodex.github.com/images/daftpunktocat-guy.gif">
styled-variants
A scalable styled-component theming system that fully leverages JavaScript as a language for styles authoring and theming at both local and global levels.
Note: This was created specifically for
styled-components
but can also be used with@emotion/styled
.
Note: the API has changed for v2. You can find the docs for v1 here
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 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.
This goals of this library are:
- Scalable theming
- Support local (passed via props) and global (passed via ThemeProvider) variants
- Support multiple variants
- Eliminate redundant code (by taking advantage of the first-class object functionality stated above)
- Minimal distribution size
We want to do this while encouraging and enabling clean, human-readable code.
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:
<!-- applies global theme to each styled component --> <!-- takes default/medium styles --> <!-- takes large styles --> <!-- takes small styles -->
The standard approach to define the "size" variant is to write a styled-component
that uses ternary switches within the template literal definition:
DIFFICULT TO READ
const Button = styledbutton` padding: ; margin: ; font-size: ; background-color: ;`;
This fails to meet our objectives on two fronts: it is difficult to read and does not scale well.
Imagine what it would look like if we had even more size options or if "size" affected more css attributes!
With styled-variants
, we can easily see:
- What is included in each variant, and
- What the css values will be without having to parse multiple levels of conditionals:
EASIER TO READ
;; const size = small: padding: "0.3em 0.7em" margin: "0.1em 0.5em" fontSize: "0.8rem" large: padding: "1em 1.2em" margin: "0.3em 0.7em" fontSize: "1.2rem" ; const mode = light: backgroundColor: "white" dark: backgroundColor: "black" ; const ButtonTheme = ; const ThemedButton = styled;
Boolean Variants
Another pain point with most theming libraries is the lack of support for multiple variants.
If we have separate states (e.g. isDisabled, isActive, isOpen, etc) for each variant, we can easily incorporate those too:
const ButtonTheme = ; const ThemedButton = styled;
Then we can pass a prop value for isDisabled
and isActive
:
const MyApp = { const isDisabled setIsDisabled = ; return <ThemeProvider theme= colors > <ThemedButton isDisabled=isDisabled type="secondary" /> <ThemedButton isDisabled=isDisabled /> <ThemedButton isDisabled=isDisabled type="secondary" isActive /> </ThemeProvider> ;};
Boolean Variant Negation
Most actionable elements (e.g. inputs, buttons, etc) will accept a disabled
or isDisabled
prop that removes hover/focus visual states. We can easily apply that logic by prefixing the boolean variant name with a !
just like in JavaScript:
const ButtonTheme = ;
Access to Props
Just like styled-components
, styled-variants
also supports passing of props via function at multiple levels:
;; const type = secondary: color: "black" themecolorssecondary themecolorsprimary ; const mode = light: backgroundColor: propsthemecolorsbackgroundlight dark: backgroundColor: propsthemecolorsbackgrounddark ; const ButtonTheme = ; const ThemedButton = styled;
Then we can use styled-components
's ThemeProvider
to inject a theme into the props of our ThemedButton
:
import ThemeProvider from "styled-components";import ThemedButton from "./ThemedButton.js"; const colors = primary: "#09d3ac" secondary: "#282c34" background: dark: "black" light: "white" ; const MyApp = return <ThemeProvider => <ThemedButton /> <ThemedButton ="secondary" /> </ThemeProvider> ;;
Multiple Variants
To add multiple variants, all you need to do is continue chaining your variants:
const type = /* previous example's type variant code */;const size = /* previous example's size variant code */; const ButtonTheme = ; const ThemedButton = styled;
Precedence is set based on the order of the function calls. The later the function is called, the greater the precedence.
In this case, if there were conflicting styles between the variants, the size
styles would overwrite the conflicting value of the type
.
Combining Themes
Thankfully, styled-components
allows for multiple sets of objects when creating a styled component, so we can do the following to combine our themes:
const type = /* previous example's type variant code */;const size = /* previous example's size variant code */; const BaseButtonTheme = ; const shape = /* shape variant code */; const ShapedButtonTheme = ; const ThemedButton = styled;
Again, precedence is set based on the function call order. In this case, if there were conflicting styles between the themes, the ShapedButtonTheme
styles would overwrite the conflicting value of the BaseButtonTheme
.
Pseudo Class Support
To add pseudo classes we need to make it a valid key. This is done simply by wrapping it in quotes:
; const type = primary: color: "green" "&:hover": color: "limegreen" secondary: color: "black" "&:hover, &:focus": color: "purple" ; const ButtonTheme = ;
Global Variant Theming
Sometimes we want to change our entire app's styles based on a ThemeProvider
value, rather than a local prop value. We can do that via the globalVariant
function:
;; const size = /* previous example's size variant code */;const mode = soft: borderRadius: "50px" hard: borderRadius: "3px" ; const ButtonTheme = ; const ThemedButton = styled;
Then, to make use of the "mode", we pass the "mode" globalVariant
via the ThemeProvider
:
const MyApp = { const mode setMode = ; return <ThemeProvider theme= mode > <ThemedButton onClick= > Set Soft Mode </ThemedButton> <ThemedButton onClick= > Set Hard Mode </ThemedButton> </ThemeProvider> ;};
Globally Theming a Specific Component Type
In previous examples, we created our theme named Button
:
const ButtonTheme = ;
If we'd like to globally style all of our components that use the Button
theme, we can do that by adding a Button
key to components
in our theme that we pass to the ThemeProvider
:
const MyApp = { return <ThemeProvider theme= colors components: Button: userSelect: "none" cursor: "pointer" isDisabled: cursor: "default" pointerEvents: "none" > <ThemedButton /> <ThemedButton isDisabled /> </ThemeProvider> ;};
Note: We do NOT currently support basic variants, but do have support for boolean variants.
FAQ
styled-system
?
Does this package work with styled-system
should be mainly used for rapid proto-typing, and styled-variants
for consistent, long-term theming. Adding styled-system
capabilities would really only lower performance and add complexity without adding too much gain, BUT the packages can be used side-by-side:
;;; const size = /* previous example's size variant code */; const ButtonTheme = ; const ThemedButton = styled; <ThemedButton size="large" m=24 textAlign="center" />
Note: Following the
styled-components
documentation, you're going to wantButtonTheme
to be the first parameter so yourstyled-system
props override any of the theme styles.
I have a super complex variant that I need to add, will this library support it?
It honestly depends on the amount of complexity, but if your variants are super complex with a lot of pseudo classes and nested dynamic selectors, odds are that it will make less sense to use this library in this case. You can very easily combine both basic styled-component
styles and styled-variant
styles:
const ButtonTheme = ; const ThemedButton = styled; const StyledButton = ` * + * { div { margin-bottom: 20px; &:hover { background-color: green; } } }`;
Contributing
Contributions are welcome. Standards have yet to be set but we will set these in the near future.
To see changes as you make them, an example app has been created. You can run it with:
$ npm run example
License
MIT