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)