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)