Styled Components
"styled-components: CSS-in-JS, tagged template literals, theming, dynamic styles, SSR, global styles, extending"
styled-components uses tagged template literals to write actual CSS inside JavaScript. Each styled call creates a React component with automatically generated, unique class names. This eliminates class-name collisions, dead-code elimination is automatic (unused components are tree-shaken), and styles live next to the components they belong to. The library supports dynamic styling via props, theming through React context, and server-side rendering with style extraction. styled-components embraces the idea that styling is a concern of the component, not of a separate stylesheet. ## Key Points 1. **Use transient props ($ prefix).** Prefix props that are only for styling with `$` (e.g., `$intent`, `$size`) so they are not forwarded to the DOM and do not cause React warnings. 2. **Augment DefaultTheme in TypeScript.** Declare your theme shape in `styled.d.ts` so every `theme` access inside tagged templates is type-checked and autocompleted. 3. **Co-locate styles with components.** Define styled components in the same file or a sibling `*.styles.ts` file rather than a central stylesheet. 4. **Prefer extending over prop overloading.** Use `styled(Base)` to create specialized variants rather than adding more conditional props to a single component. 5. **Register styles for SSR.** In Next.js App Router, use `useServerInsertedHTML` with `ServerStyleSheet` to avoid flash of unstyled content. 6. **Use `css` helper for shared fragments.** Extract common CSS blocks with the `css` helper function and interpolate them across styled components. 2. **Using `attrs` for frequently changing values.** `attrs` for dynamic inline styles is fine, but using it for static configuration that should be props makes the API harder to understand. 3. **Deep prop-drilling for theme values.** Access theme values via the `theme` callback in tagged templates. Do not destructure the theme in parent components and pass individual values as props. ## Quick Example ```bash npm install styled-components npm install -D @types/styled-components ``` ```bash npm install -D babel-plugin-styled-components ```
skilldb get css-styling-services-skills/Styled ComponentsFull skill: 391 linesstyled-components
Core Philosophy
styled-components uses tagged template literals to write actual CSS inside JavaScript. Each styled call creates a React component with automatically generated, unique class names. This eliminates class-name collisions, dead-code elimination is automatic (unused components are tree-shaken), and styles live next to the components they belong to. The library supports dynamic styling via props, theming through React context, and server-side rendering with style extraction. styled-components embraces the idea that styling is a concern of the component, not of a separate stylesheet.
Setup
Installation
npm install styled-components
npm install -D @types/styled-components
Babel Plugin (Optional, Recommended)
npm install -D babel-plugin-styled-components
// .babelrc
{
"plugins": [
[
"babel-plugin-styled-components",
{
"displayName": true,
"ssr": true,
"meaninglessFileNames": ["index", "styles"]
}
]
]
}
Next.js Configuration
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
compiler: {
styledComponents: true,
},
};
export default nextConfig;
SSR Registry for Next.js App Router
// lib/styled-components-registry.tsx
"use client";
import React, { useState } from "react";
import { useServerInsertedHTML } from "next/navigation";
import { ServerStyleSheet, StyleSheetManager } from "styled-components";
export function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode;
}) {
const [sheet] = useState(() => new ServerStyleSheet());
useServerInsertedHTML(() => {
const styles = sheet.getStyleElement();
sheet.instance.clearTag();
return <>{styles}</>;
});
if (typeof window !== "undefined") return <>{children}</>;
return (
<StyleSheetManager sheet={sheet.instance}>{children}</StyleSheetManager>
);
}
// app/layout.tsx
import { StyledComponentsRegistry } from "@/lib/styled-components-registry";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
);
}
Key Techniques
Defining a Theme
// theme.ts
export const lightTheme = {
colors: {
brand: "#3b82f6",
brandDark: "#1d4ed8",
surface: "#ffffff",
textPrimary: "#111827",
textSecondary: "#6b7280",
border: "#e5e7eb",
danger: "#ef4444",
success: "#22c55e",
},
space: {
xs: "0.25rem",
sm: "0.5rem",
md: "1rem",
lg: "1.5rem",
xl: "2rem",
"2xl": "3rem",
},
radii: {
sm: "0.25rem",
md: "0.5rem",
lg: "0.75rem",
full: "9999px",
},
fontSizes: {
sm: "0.875rem",
base: "1rem",
lg: "1.125rem",
xl: "1.25rem",
"2xl": "1.5rem",
"3xl": "1.875rem",
},
shadows: {
sm: "0 1px 2px rgba(0,0,0,0.05)",
md: "0 4px 6px rgba(0,0,0,0.07)",
lg: "0 10px 15px rgba(0,0,0,0.1)",
},
} as const;
export const darkTheme: typeof lightTheme = {
colors: {
brand: "#60a5fa",
brandDark: "#3b82f6",
surface: "#1f2937",
textPrimary: "#f9fafb",
textSecondary: "#9ca3af",
border: "#374151",
danger: "#f87171",
success: "#4ade80",
},
space: lightTheme.space,
radii: lightTheme.radii,
fontSizes: lightTheme.fontSizes,
shadows: {
sm: "0 1px 2px rgba(0,0,0,0.3)",
md: "0 4px 6px rgba(0,0,0,0.4)",
lg: "0 10px 15px rgba(0,0,0,0.5)",
},
};
export type AppTheme = typeof lightTheme;
TypeScript Theme Augmentation
// styled.d.ts
import "styled-components";
import type { AppTheme } from "./theme";
declare module "styled-components" {
export interface DefaultTheme extends AppTheme {}
}
Theme Provider
"use client";
import { ThemeProvider } from "styled-components";
import { lightTheme, darkTheme } from "@/theme";
import { useThemeMode } from "@/hooks/useThemeMode";
export function AppThemeProvider({ children }: { children: React.ReactNode }) {
const { mode } = useThemeMode();
return (
<ThemeProvider theme={mode === "dark" ? darkTheme : lightTheme}>
{children}
</ThemeProvider>
);
}
Styled Components with Props
import styled from "styled-components";
interface ButtonProps {
$intent?: "primary" | "secondary" | "danger";
$size?: "sm" | "md" | "lg";
}
const sizeMap = {
sm: { fontSize: "sm", px: "sm", py: "xs" },
md: { fontSize: "base", px: "md", py: "sm" },
lg: { fontSize: "lg", px: "lg", py: "md" },
} as const;
export const Button = styled.button<ButtonProps>`
display: inline-flex;
align-items: center;
justify-content: center;
gap: ${({ theme }) => theme.space.sm};
font-weight: 500;
border: none;
border-radius: ${({ theme }) => theme.radii.md};
cursor: pointer;
transition: background-color 0.15s ease, box-shadow 0.15s ease;
font-size: ${({ theme, $size = "md" }) => theme.fontSizes[sizeMap[$size].fontSize]};
padding: ${({ theme, $size = "md" }) =>
`${theme.space[sizeMap[$size].py]} ${theme.space[sizeMap[$size].px]}`};
background-color: ${({ theme, $intent = "primary" }) => {
if ($intent === "primary") return theme.colors.brand;
if ($intent === "danger") return theme.colors.danger;
return "transparent";
}};
color: ${({ theme, $intent = "primary" }) =>
$intent === "secondary" ? theme.colors.textPrimary : "#fff"};
border: ${({ theme, $intent }) =>
$intent === "secondary" ? `1px solid ${theme.colors.border}` : "none"};
&:hover:not(:disabled) {
background-color: ${({ theme, $intent = "primary" }) => {
if ($intent === "primary") return theme.colors.brandDark;
if ($intent === "danger") return "#dc2626";
return theme.colors.surface;
}};
}
&:focus-visible {
outline: 2px solid ${({ theme }) => theme.colors.brand};
outline-offset: 2px;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
Extending Components
const Card = styled.div`
background: ${({ theme }) => theme.colors.surface};
border: 1px solid ${({ theme }) => theme.colors.border};
border-radius: ${({ theme }) => theme.radii.lg};
padding: ${({ theme }) => theme.space.lg};
box-shadow: ${({ theme }) => theme.shadows.sm};
`;
const ElevatedCard = styled(Card)`
box-shadow: ${({ theme }) => theme.shadows.lg};
border: none;
transition: transform 0.2s ease;
&:hover {
transform: translateY(-2px);
}
`;
const CardTitle = styled.h3`
font-size: ${({ theme }) => theme.fontSizes.xl};
font-weight: 600;
color: ${({ theme }) => theme.colors.textPrimary};
margin-bottom: ${({ theme }) => theme.space.sm};
`;
const CardDescription = styled.p`
font-size: ${({ theme }) => theme.fontSizes.base};
color: ${({ theme }) => theme.colors.textSecondary};
line-height: 1.6;
`;
Global Styles
import { createGlobalStyle } from "styled-components";
export const GlobalStyles = createGlobalStyle`
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
}
body {
background-color: ${({ theme }) => theme.colors.surface};
color: ${({ theme }) => theme.colors.textPrimary};
line-height: 1.5;
}
a {
color: ${({ theme }) => theme.colors.brand};
text-decoration: none;
&:hover { text-decoration: underline; }
}
`;
Composition Pattern
function ProfileCard({ user }: { user: User }) {
return (
<ElevatedCard>
<AvatarRow>
<Avatar src={user.avatar} alt={user.name} />
<div>
<CardTitle>{user.name}</CardTitle>
<CardDescription>{user.bio}</CardDescription>
</div>
</AvatarRow>
<Actions>
<Button $intent="primary" $size="sm">Follow</Button>
<Button $intent="secondary" $size="sm">Message</Button>
</Actions>
</ElevatedCard>
);
}
const AvatarRow = styled.div`
display: flex;
align-items: center;
gap: ${({ theme }) => theme.space.md};
`;
const Avatar = styled.img`
width: 4rem;
height: 4rem;
border-radius: ${({ theme }) => theme.radii.full};
object-fit: cover;
`;
const Actions = styled.div`
display: flex;
gap: ${({ theme }) => theme.space.sm};
margin-top: ${({ theme }) => theme.space.lg};
`;
Best Practices
- Use transient props ($ prefix). Prefix props that are only for styling with
$(e.g.,$intent,$size) so they are not forwarded to the DOM and do not cause React warnings. - Augment DefaultTheme in TypeScript. Declare your theme shape in
styled.d.tsso everythemeaccess inside tagged templates is type-checked and autocompleted. - Co-locate styles with components. Define styled components in the same file or a sibling
*.styles.tsfile rather than a central stylesheet. - Prefer extending over prop overloading. Use
styled(Base)to create specialized variants rather than adding more conditional props to a single component. - Register styles for SSR. In Next.js App Router, use
useServerInsertedHTMLwithServerStyleSheetto avoid flash of unstyled content. - Use
csshelper for shared fragments. Extract common CSS blocks with thecsshelper function and interpolate them across styled components.
Anti-Patterns
- Defining styled components inside render functions. This creates a new component class on every render, destroying React reconciliation and causing remounts. Always define styled components outside the component body.
- Using
attrsfor frequently changing values.attrsfor dynamic inline styles is fine, but using it for static configuration that should be props makes the API harder to understand. - Deep prop-drilling for theme values. Access theme values via the
themecallback in tagged templates. Do not destructure the theme in parent components and pass individual values as props. - Ignoring bundle size on RSC routes. styled-components requires a client-side runtime. In React Server Component architectures, this means wrapping pages in
"use client"boundaries. Consider whether a zero-runtime alternative is more appropriate for server-heavy pages. - Overusing
createGlobalStyle. Global styles should be limited to CSS resets, font-face declarations, and root variables. Component-specific styles should always be scoped to a styled component.
Install this skill directly: skilldb add css-styling-services-skills
Related Skills
CSS Modules
"CSS Modules: scoped CSS, composition, global styles, Next.js integration, TypeScript declarations, naming conventions"
Lightning CSS
"Lightning CSS: ultra-fast CSS transformer, bundler, and minifier written in Rust, with modern syntax lowering, vendor prefixing, and CSS modules"
Panda CSS
"Panda CSS: build-time CSS-in-JS, atomic CSS, recipes, patterns, tokens, conditions, RSC compatible, type-safe styles"
Stitches
"Stitches: near-zero runtime CSS-in-JS, variants API, theme tokens, responsive styles, SSR support, polymorphic components"
Tailwind CSS
"Tailwind CSS: utility-first CSS, responsive design, dark mode, custom theme, plugins, @apply, JIT, content configuration, arbitrary values"
UnoCSS
"UnoCSS: instant on-demand atomic CSS engine, presets, shortcuts, rules, variants, attributify mode, icon support, inspector"