Are you familiarized with React's Context API? From styled-components docs:
styled-componentshas full theming support by exporting a<ThemeProvider>wrapper component. This component provides a theme to all React components underneath itself via the context API.
Let's see how we could implement a ThemeProvider with styled-components.
1. First we need to create a Context to encapsulate our theming logic
We should also create a "custom hook" to ease access to our context throughout our app.
theme-context.jsx
import React from 'react'
export const ThemeContext = React.createContext({
// our theme object
theme: {},
// our color modes ('dark' || 'light')
colorMode: '',
// a method to toggle our theme from `dark` to `light` and vice-versa
setColorMode: () => null,
})
// export our custom hook for quick access to our context
export function useTheme() {
return React.useContext(ThemeContext)
}
2. Now we need to extend styled-components native <ThemeProvider> to create our own ThemeProvider
Since we'll need access to our themes, I'll add two very contrived theme objects (for the sake of simplicity) as well.
theme-provider.jsx
import React from 'react'
import { ThemeProvider as StyledProvider } from 'styled-components'
import { ThemeContext } from './theme-context'
// our theme objects
const lightTheme = { colorMode: 'light', bg: '#fff', text: '#000' }
const darkTheme = { colorMode: 'dark', bg: '#000', text: '#fff' }
// our iterable theme "store"
const myThemes = [lightTheme, darkTheme]
// our default color mode
const defaultColorMode = 'light'
const ThemeProvider = ({ children, ...props }) => {
// get fallback values from the parent ThemeProvider (if exists)
const {
theme: fallbackTheme,
colorMode: fallbackColorMode,
} = useTheme()
// initialize our state
const theme = props.theme ?? fallbackTheme
const [colorMode, setColorMode] = React.useState(
props.colorMode ?? fallbackColorMode ?? defaultColorMode,
)
// memoize the current theme
const resolvedTheme = React.useMemo(() => {
const theme = myThemes.find(t => t.colorMode === colorMode)
if (theme) return theme
return lightTheme
}, [theme, myThemes, colorMode])
// update our state if props change
React.useEffect(() => {
setColorMode(props.colorMode ?? fallbackColorMode ?? defaultColorMode)
}, [props.colorMode, fallbackColorMode])
return (
<ThemeContext.Provider
value={{
theme: resolvedTheme,
colorMode,
setColorMode,
}}
>
<StyledProvider theme={resolvedTheme}>{children}</StyledProvider>
</ThemeContext.Provider>
)
}
export default ThemeProvider
3. Our final step is wrapping up our main <App /> component within our ThemeProvider
app.jsx
import React from 'react'
import ThemeProvider from './theme-provider'
const App = () => {
const [themeType, setThemeType] = React.useState('light')
const switchThemes = () => {
setThemeType(last => (last === 'dark' ? 'light' : 'dark'))
}
return (
<ThemeProvider colorMode={themeType}>
<MySwitch onClick={switchThemes} />
</ThemeProvider>
)
}
And that's it. We should now be able to toggle our theme by clicking on MySwitch. Hope that helps!
Let me know how it goes? Cheers
Answer from Moa on Stack OverflowDefault theme for components without parent ThemeProvider
Using Styled-Components with a theme provider. How should I organize my colors?
Implement different themes using styled components
reactjs - How to get the theme outside styled-components? - Stack Overflow
Videos
I'm making a design system for our company using Styled Components. I can create a theme and use that theme for any component that needs to reuse padding, colors, etc.
export const Theme = (props) => (
<ThemeProvider theme={{
fontWeights: {
bold: 700;
normal: 400,
light: 100
},
...
}}>{props.children}</ThemeProvider>
);
export const Button = styled.button(props => `
font-weight: props.theme.fontWeight.normal,
...
`);The problem I'm having is colors. I don't want to specify the same HTML color codes everywhere, so I want to have them in the theme. But how do I organize them?
If I try this solution, then when I find a new situation I need to amend the color object, and it grows out of control with duplicated values and inconsistent variable names:
theme = {
colors: {
success: {
font: "#2dce89",
disabledBackground: "#abebd0",
glow: ...
active: ...
borderDisabled: ...
...
}
}
}Design sites suggest using numbers. But if I have to style a button, how do I know which color weight to use? And where do greyed versions for disabled go? What about other variants?
success: {
100: "#d5f5e7",
200: "#abebd0",
300: "#81e2b8",
400: "#57d8a1",
500: "#2dce89",
600: "#24a56e",
700: "#1b7c52",
800: "#125237",
900: "#09291b",
},Is there a better way to organize my colors so it is easy to be consistent across multiple components without having an explosion of one-off variables or duplicated values?
You can use the useTheme hook since v5.0:
import React, { useTheme } from 'styled-components';
export function MyComponent() {
const theme = useTheme();
return <p style={{ color: theme.color }}>Text</p>;
}
You can also use the withTheme higher order component that I contributed a long time ago since v1.2:
import { withTheme } from 'styled-components'
class MyComponent extends React.Component {
render() {
const { theme } = this.props
console.log('Current theme: ', theme);
// ...
}
}
export default withTheme(MyComponent)
original response below (ignore this!)
While there is no official solution, I came up by now:
Create a Higher Order Component that will be responsable to get the current theme and pass as a prop to a component:
import React from 'react';
import { CHANNEL } from 'styled-components/lib/models/ThemeProvider';
export default Component => class extends React.Component {
static contextTypes = {
[CHANNEL]: React.PropTypes.func,
};
state = {
theme: undefined,
};
componentWillMount() {
const subscribe = this.context[CHANNEL];
this.unsubscribe = subscribe(theme => {
this.setState({ theme })
});
}
componentWillUnmount() {
if (typeof this.unsubscribe === 'function') this.unsubscribe();
}
render() {
const { theme } = this.state;
return <Component theme={theme} {...this.props} />
}
}
Then, call it on the component you need to access the theme:
import Themable from './Themable.js'
const Component = ({ theme }) => <Card color={theme.color} />
export default Themable(Component);
You can use useTheme hook
import { useTheme } from 'styled-components';
const ExampleComponent = () => {
const theme = useTheme();
return (
<View>
<Card aCustomColorProperty={theme.color.sampleColor} />
</View>
);
};