Fix: styled-components Not Working — Styles Not Applying, SSR Flash of Unstyled Content, or Theme Not Available
Quick Answer
How to fix styled-components issues — ThemeProvider setup, Next.js SSR with ServerStyleSheet, shouldForwardProp, attrs, TypeScript theme typing, and styled-components v6 migration.
The Problem
Styled components render with no styles or incorrect styles:
const Button = styled.button`
background: blue;
color: white;
padding: 8px 16px;
`;
// <Button> renders as a plain button with no stylesOr theme values aren’t accessible inside a styled component:
const Text = styled.p`
color: ${props => props.theme.colors.primary};
// TypeError: Cannot read property 'colors' of undefined
`;Or Next.js pages flash unstyled content on first load:
// Page loads with no styles for a fraction of a second, then styles appearOr a custom prop causes a React warning:
Warning: Received `true` for a non-boolean attribute `isActive`.Why This Happens
styled-components has specific setup requirements per environment:
ThemeProvidermust wrap all styled components that use the theme — if a styled component usingprops.themerenders outsideThemeProvider, the theme is{}orundefined. Missing the provider at the app root is the most common cause.- SSR without
ServerStyleSheetcauses FOUC — styled-components generates class names at runtime. Without the SSR setup, styles are generated after the HTML is sent, causing a flash of unstyled content until JavaScript runs. - Custom props forwarded to DOM elements cause warnings — any prop you pass to a styled component that starts with a lowercase letter is forwarded to the underlying HTML element. Boolean or string custom props on DOM elements trigger React warnings. Use
shouldForwardPropor prefix with$(transient props in v5.1+). - CSS vendor prefixes — styled-components auto-applies them — if you’re seeing missing styles in older browsers, styled-components v5+ automatically prefixes. v6 removed the stylis CSS preprocessor by default for smaller bundle size.
Fix 1: Set Up ThemeProvider
// theme.ts — define your theme
export const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
background: '#ffffff',
text: '#212529',
error: '#dc3545',
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
},
typography: {
fontFamily: "'Inter', sans-serif",
sizes: {
sm: '14px',
md: '16px',
lg: '18px',
xl: '24px',
},
},
borderRadius: {
sm: '4px',
md: '8px',
lg: '12px',
full: '9999px',
},
};
export type Theme = typeof theme;// App.tsx — ThemeProvider must wrap everything
import { ThemeProvider } from 'styled-components';
import { theme } from './theme';
function App() {
return (
<ThemeProvider theme={theme}>
{/* All styled components here can access the theme */}
<Router>
<Layout />
</Router>
</ThemeProvider>
);
}TypeScript theme typing — avoid any in theme access:
// styled.d.ts — augment the DefaultTheme interface
import 'styled-components';
import { Theme } from './theme';
declare module 'styled-components' {
export interface DefaultTheme extends Theme {}
}
// Now props.theme is fully typed
const Button = styled.button`
background: ${props => props.theme.colors.primary}; // Typed!
padding: ${props => props.theme.spacing.md};
`;Fix 2: Fix Next.js SSR with styled-components
For Next.js App Router (recommended — use the compiler):
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
compiler: {
styledComponents: true, // Enable Next.js built-in styled-components support
},
};
module.exports = nextConfig;This handles SSR automatically without ServerStyleSheet. Restart the dev server after adding this.
For Next.js Pages Router (legacy approach with ServerStyleSheet):
// pages/_document.tsx
import Document, { DocumentContext, Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}Verify SSR is working:
# Check the HTML source — styles should be in a <style> tag in the <head>
curl http://localhost:3000 | grep styled
# Should see something like:
# <style data-styled="active" data-styled-version="6.x.x">...</style>Fix 3: Fix Prop Forwarding Warnings
// WRONG — isActive is forwarded to <div> as a DOM attribute
const Panel = styled.div<{ isActive: boolean }>`
background: ${props => props.isActive ? 'blue' : 'gray'};
`;
// Warning: Received `true` for a non-boolean attribute `isActive`
// FIX 1 — Transient props ($ prefix, v5.1+, doesn't forward to DOM)
const Panel = styled.div<{ $isActive: boolean }>`
background: ${props => props.$isActive ? 'blue' : 'gray'};
`;
// Usage: <Panel $isActive={true}>
// FIX 2 — shouldForwardProp (explicit control)
const Panel = styled.div.withConfig({
shouldForwardProp: (prop) => !['isActive', 'size', 'variant'].includes(prop),
})<{ isActive: boolean }>`
background: ${props => props.isActive ? 'blue' : 'gray'};
`;
// FIX 3 — Destructure in attrs
const Panel = styled.div.attrs<{ isActive: boolean }>(props => ({
// Map to data attributes or remove from forwarding
'data-active': props.isActive,
}))`
background: ${props => props.isActive ? 'blue' : 'gray'};
`;Global shouldForwardProp configuration (v6):
import { StyleSheetManager, isPropValid } from 'styled-components';
function App() {
return (
<StyleSheetManager shouldForwardProp={isPropValid}>
{/* isPropValid filters out non-HTML props automatically */}
<YourApp />
</StyleSheetManager>
);
}Fix 4: Dynamic Styles and Variants
// Button with multiple variants
interface ButtonProps {
$variant?: 'primary' | 'secondary' | 'danger';
$size?: 'sm' | 'md' | 'lg';
$fullWidth?: boolean;
}
const Button = styled.button<ButtonProps>`
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: all 0.15s;
/* Size variants */
${({ $size = 'md' }) => {
const sizes = {
sm: 'height: 32px; padding: 0 12px; font-size: 13px;',
md: 'height: 40px; padding: 0 16px; font-size: 14px;',
lg: 'height: 48px; padding: 0 24px; font-size: 16px;',
};
return sizes[$size];
}}
/* Color variants */
${({ $variant = 'primary', theme }) => {
const variants = {
primary: `
background: ${theme.colors.primary};
color: white;
&:hover { background: ${theme.colors.primary}dd; }
`,
secondary: `
background: ${theme.colors.secondary};
color: white;
&:hover { background: ${theme.colors.secondary}dd; }
`,
danger: `
background: ${theme.colors.error};
color: white;
&:hover { background: ${theme.colors.error}dd; }
`,
};
return variants[$variant];
}}
${({ $fullWidth }) => $fullWidth && 'width: 100%;'}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
// Usage
<Button $variant="primary" $size="lg" onClick={handleClick}>
Submit
</Button>Using css helper for reusable style blocks:
import styled, { css } from 'styled-components';
const focusRing = css`
outline: 2px solid ${props => props.theme.colors.primary};
outline-offset: 2px;
`;
const Input = styled.input`
border: 1px solid #ccc;
border-radius: 4px;
padding: 8px 12px;
&:focus {
${focusRing} // Reuse the style block
border-color: ${props => props.theme.colors.primary};
}
`;
// Conditional CSS block
const Card = styled.div<{ $elevated?: boolean }>`
background: white;
border-radius: 8px;
${({ $elevated }) => $elevated && css`
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
`}
`;Fix 5: Global Styles and Keyframes
import { createGlobalStyle, keyframes } from 'styled-components';
// Global styles
export const GlobalStyle = createGlobalStyle`
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
-webkit-text-size-adjust: 100%;
}
body {
font-family: ${({ theme }) => theme.typography.fontFamily};
background-color: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.text};
line-height: 1.5;
}
a {
color: ${({ theme }) => theme.colors.primary};
text-decoration: none;
&:hover { text-decoration: underline; }
}
`;
// Add to App.tsx
function App() {
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Router>
<Layout />
</Router>
</ThemeProvider>
);
}
// Keyframes
const fadeIn = keyframes`
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
`;
const spin = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
const LoadingSpinner = styled.div`
width: 24px;
height: 24px;
border: 3px solid #f3f3f3;
border-top: 3px solid ${props => props.theme.colors.primary};
border-radius: 50%;
animation: ${spin} 0.8s linear infinite;
`;
const FadeInCard = styled.div`
animation: ${fadeIn} 0.3s ease-out;
`;Fix 6: Migrate to styled-components v6
v6 introduced breaking changes from v5:
# Upgrade
npm install styled-components@latest
# Check for breaking changes
npx @styled-components/codemod v6/no-tags # Auto-fix some patternsKey v6 changes:
// 1. Stylis (CSS preprocessor) removed — no auto-vendor-prefixing
// Install if needed:
npm install @styled-components/stylis
// 2. No automatic type inference for theme
// Must use DefaultTheme augmentation (see Fix 1)
// 3. Tagged template literals with interpolated styled components
// v5 — works:
const Wrapper = styled.div`
${Button} { color: red; }
`;
// v6 — same, still works, but verify with your setup
// 4. transient props ($) — same as v5.1+
// No change
// 5. createGlobalStyle — minor API changes
// Test your global styles after upgrade
// 6. TypeScript — more strict types
// May need to update component prop typesStill Not Working?
Styles applied in dev but missing in production — class name hashing changed between environments. Enable deterministic class names:
// next.config.js
module.exports = {
compiler: {
styledComponents: {
displayName: true,
ssr: true,
fileName: true,
meaninglessFileNames: ['index'],
minify: true,
transpileTemplateLiterals: true,
},
},
};Or via Babel:
// .babelrc
{
"plugins": [
["babel-plugin-styled-components", {
"ssr": true,
"displayName": true,
"preprocess": false
}]
]
}Multiple styled-components instances (causes theme loss) — if your app and a library both import styled-components, React may use two separate instances. Both must use the same instance:
# Check for duplicate instances
npm ls styled-components
# Should show only one version
# Fix with npm dedupe or by aliasing
npm dedupeStyles lost after React hot reload — the Babel plugin or Next.js compiler handles this. Without it, class names regenerate on hot reload, breaking stylesheet injection. Verify the compiler config is active.
For related styling issues, see Fix: CSS Tailwind Not Applying and Fix: shadcn/ui Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AutoAnimate Not Working — Transitions Not Playing, List Items Not Animating, or React State Changes Ignored
How to fix @formkit/auto-animate issues — parent ref setup, React useAutoAnimate hook, Vue directive, animation customization, disabling for specific elements, and framework integration.
Fix: Blurhash Not Working — Placeholder Not Rendering, Encoding Failing, or Colors Wrong
How to fix Blurhash image placeholder issues — encoding with Sharp, decoding in React, canvas rendering, Next.js image placeholders, CSS blur fallback, and performance optimization.
Fix: Embla Carousel Not Working — Slides Not Scrolling, Autoplay Not Starting, or Thumbnails Not Syncing
How to fix Embla Carousel issues — React setup, slide sizing, autoplay and navigation plugins, loop mode, thumbnail carousels, responsive breakpoints, and vertical scrolling.
Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.