Dynamic Theming with styled-components: Switching Themes at Runtime
Learn how to implement runtime theme switching in styled-components using ThemeProvider, React context, and a practical light/dark toggle example.
23 Jan 2026, 00:28 UTC

Problem: Hard-coded colors make UI updates painful
When every button, card, or modal defines its own color values, a design tweak forces you to hunt through dozens of files. The effort grows with the codebase and increases the chance of inconsistency.
Thesis: Use ThemeProvider and React context to centralize design tokens and swap themes on the fly
By wrapping the component tree with ThemeProvider from styled-components, a single theme object becomes available to every styled component. Changing that object updates all downstream styles instantly, giving you a runtime-switchable light/dark mode (or any theme variant) without touching individual styles.
How ThemeProvider works
The ThemeProvider component uses React's Context API. It accepts a theme prop and makes that value accessible via the props.theme argument inside any styled component's template literal:
const Button = styled.button`
background: ${props => props.theme.colors.primary};
color: ${props => props.theme.colors.onPrimary};
padding: ${props => props.theme.space[2]};
`;
When the theme prop changes, Context notifies all consumers, causing a re-render of the styled components that read the theme.
Worked example: Light/Dark toggle
- Setup – Ensure you have a React project with
styled-componentsinstalled (npm i styled-componentsoryarn add styled-components). No special permissions are required beyond editing source files. - Create theme objects (
src/theme.js):export const light = { colors: { primary: '#1976d2', onPrimary: '#ffffff', background: '#fafafa' }, space: [0, '4px', '8px', '16px'] }; export const dark = { colors: { primary: '#90caf9', onPrimary: '#000000', background: '#212121' }, space: [0, '4px', '8px', '16px'] }; - Consume the theme in a styled component (
src/Button.js):import styled from 'styled-components'; export const Button = styled.button` background: ${props => props.theme.colors.primary}; color: ${props => props.theme.colors.onPrimary}; padding: ${props => props.theme.space[2]}; border: none; cursor: pointer; `; - Wrap the app and toggle state (
src/App.js):import { ThemeProvider } from 'styled-components'; import { light, dark } from './theme'; import { useState } from 'react'; import { Button } from './Button'; function App() { const [isDark, setIsDark] = useState(false); const theme = isDark ? dark : light; return ( <ThemeProvider theme={theme}> <div style={{ padding: '2rem', background: theme.colors.background, minHeight: '100vh' }}> <Button onClick={() => setIsDark(!isDark)}> Toggle theme </Button> <Button>Primary action</Button> </div> </ThemeProvider> ); } export default App; - Verification – Open the app in a browser, click "Toggle theme", and use the developer tools inspector to confirm that the
backgroundandcolorvalues of the buttons change accordingly. No console errors should appear.
Trade-off: Performance vs flexibility
Every theme update triggers a Context change, which causes all styled components that read the theme to re-render. In a large UI this can lead to unnecessary work if the theme changes frequently (e.g., on every animation frame). Mitigation strategies include:
- Memoizing heavy sub-trees with
React.memooruseMemo. - Updating only the specific tokens that actually changed, rather than replacing the whole theme object.
- Using
styled-components'swithThemehigher-order component sparingly.
For most applications, a theme toggle on user action (light/dark mode) incurs negligible overhead, but keep an eye on re-render frequency if you drive theme changes from high-frequency events like mouse movement.
Actionable closing
Start by extracting your current color and spacing values into a simple theme object, wrap your app with ThemeProvider, and replace hard-coded values with ${props => props.theme.xxx} references. Test the toggle, measure render impact with the React Profiler if needed, and iterate on token granularity. This gives you a maintainable, runtime-switchable design system without sacrificing the flexibility of styled-components.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.