TL;DR — We’ll stand up a tiny project that still scales: a single, strongly-typed
theme
, colocated global styles, tiny reusable components, and pages that stay blissfully unaware of CSS. Grab the code, paste it into a fresh Vite/CRA repo, and you’re off to the races.
1 · Project layout
my-app/
├── package.json
├── tsconfig.json
├── public/
│ └── index.html
└── src/
├── index.tsx ← React entry point
├── App.tsx ← root layout + providers
├── styles/
│ ├── theme.ts ← palette, spacing helpers, etc.
│ ├── GlobalStyles.ts← reset + global styles
│ └── styled.d.ts ← module augmentation for typed theme
├── components/
│ ├── Button.tsx
│ └── Card.tsx
└── pages/
└── Home.tsx
This folder-per-concern approach keeps your design system (styles
+ components
) separate from features (pages
). Swap in feature-first
folders later without touching the underlying building blocks.
2 · The theme (src/styles/theme.ts
)
import { DefaultTheme } from "styled-components";
export const theme: DefaultTheme = {
colors: {
primary: "#1e88e5",
secondary: "#ff7043",
background: "#f5f7fa",
text: "#1a1a1a",
},
spacing: (factor: number) => `${0.25 * factor}rem`,
};
A single source of design truth. Bump the primary color or spacing scale once, every component updates instantly.
3 · Type augmentation (src/styles/styled.d.ts
)
import "styled-components";
declare module "styled-components" {
export interface DefaultTheme {
colors: {
primary: string;
secondary: string;
background: string;
text: string;
};
spacing: (factor: number) => string;
}
}
styled-components
now autocompletes your palette and spacing helpers in every file—no more magic strings.
4 · Global styles (src/styles/GlobalStyles.ts
)
import { createGlobalStyle } from "styled-components";
export const GlobalStyles = createGlobalStyle`
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, sans-serif;
background: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.text};
}
`;
A minimal reset + theme-aware body styles.
5 · A typed Button
component (src/components/Button.tsx
)
import styled from "styled-components";
type ButtonProps = {
variant?: "primary" | "secondary";
};
export const Button = styled.button<ButtonProps>`
border: none;
cursor: pointer;
padding: ${({ theme }) => theme.spacing(3)} ${({ theme }) => theme.spacing(4)};
border-radius: 4px;
font-weight: 600;
background: ${({ variant = "primary", theme }) =>
variant === "primary" ? theme.colors.primary : theme.colors.secondary};
color: #fff;
transition: opacity 0.2s;
&:hover { opacity: 0.9; }
`;
The generic <ButtonProps>
unlocks variant-safe theming without prop drilling.
6 · A tidy Card
component (src/components/Card.tsx
)
import styled from "styled-components";
export const Card = styled.div`
background: #fff;
border-radius: 6px;
padding: ${({ theme }) => theme.spacing(6)};
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
`;
7 · A sample page (src/pages/Home.tsx
)
import { Card } from "../components/Card";
import { Button } from "../components/Button";
export default function Home() {
return (
<Card>
<h1>Welcome!</h1>
<p>This page uses styled-components + TypeScript.</p>
<Button variant="primary">Do the thing</Button>
</Card>
);
}
Page files focus purely on product logic, not style details.
8 · App root (src/App.tsx
)
import { ThemeProvider } from "styled-components";
import { theme } from "./styles/theme";
import { GlobalStyles } from "./styles/GlobalStyles";
import Home from "./pages/Home";
export default function App() {
return (
<ThemeProvider theme={theme}>
<GlobalStyles />
<Home />
</ThemeProvider>
);
}
All theme plumbing lives once at the root.
9 · Entry point (src/index.tsx
)
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
);
10 · Why this setup works
✅ Benefit | How it helps in real life |
---|---|
Single source of design | Change a color or spacing scale once, everywhere updates. |
Full TypeScript safety | No more typos in theme keys; IDE hints for every property. |
Isolation of concerns | Components are portable; pages hold only business logic. |
Drop-in scalability | Switch to a feature-first folder layout without refactor. |
11 · Next steps
- Storybook — document your design system as you grow.
-
Dark mode — add
theme.dark.ts
and toggle with React context. -
Utility helpers — use
polished
orcolor
for runtime color maths. -
Performance — enable
babel-plugin-styled-components
for dead code elimination and better display names.
Top comments (0)