DEV Community

A0mineTV
A0mineTV

Posted on

A Minimal Yet Scalable React + TypeScript + styled-components Architecture

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 
Enter fullscreen mode Exit fullscreen mode

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`, }; 
Enter fullscreen mode Exit fullscreen mode

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; } } 
Enter fullscreen mode Exit fullscreen mode

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}; } `; 
Enter fullscreen mode Exit fullscreen mode

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; } `; 
Enter fullscreen mode Exit fullscreen mode

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); `; 
Enter fullscreen mode Exit fullscreen mode

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> ); } 
Enter fullscreen mode Exit fullscreen mode

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> ); } 
Enter fullscreen mode Exit fullscreen mode

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> ); 
Enter fullscreen mode Exit fullscreen mode

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 or color for runtime color maths.
  • Performance — enable babel-plugin-styled-components for dead code elimination and better display names.

Top comments (0)