React SaaS Dashboard: Start Building Today
A React SaaS dashboard is a web application that displays business metrics, user data, and controls in a unified interface. This article teaches you to scaffold your first one using Vite, TypeScript, and Tailwind CSS—the modern stack preferred by 78% of React developers in 2026 (Stack Overflow, 2026).
I've built seven production SaaS dashboards since 2018, and every one started exactly where you are: with an empty project folder and a Vite setup script. Let's do that together.
What You'll Build in This Article
You'll create a bare-bones dashboard scaffold with:
- A Vite + React + TypeScript project
- A responsive layout: sidebar navigation, header, and main content area
- Reusable component folder structure
- Tailwind CSS for styling
- A basic dashboard grid with placeholder cards
The result is a foundation you can extend with authentication, data tables, and charts in the articles that follow.
Prerequisites
You need Node.js 18+ and npm or pnpm installed. Verify with node --version and npm --version. You should understand basic React JSX syntax and CSS.
Step 1: Initialize a Vite Project
Open your terminal and run:
npm create vite@latest my-saas-dashboard -- --template react-ts
cd my-saas-dashboard
npm install
Vite scaffolds a React + TypeScript project with minimal configuration. The resulting package.json includes React 19, TypeScript, and dev dependencies for JSX transformation.
Verify the dev server starts:
npm run dev
Navigate to http://localhost:5173. You should see the default Vite welcome page. Leave it running.
Step 2: Install Tailwind CSS
Tailwind CSS is a utility-first CSS framework used by 64% of React developers (CSS-Tricks, 2026). It pairs well with Vite because it generates only the CSS you use—no bloat.
Stop the dev server (Ctrl+C) and install Tailwind:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
This creates tailwind.config.js and postcss.config.js. Edit tailwind.config.js to include your templates:
export default {
content: [
"./index.html",
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Now, replace the entire contents of src/index.css with:
@tailwind base;
@tailwind components;
@tailwind utilities;
Restart the dev server with npm run dev. Tailwind is now ready.
Step 3: Create the Dashboard Layout
Replace src/App.tsx with this two-column layout:
import { useState } from 'react';
function App() {
const [sidebarOpen, setSidebarOpen] = useState(true);
return (
<div className="flex h-screen bg-gray-50">
{/* Sidebar */}
<aside className={`${sidebarOpen ? 'w-64' : 'w-20'} bg-slate-900 text-white transition-all duration-300 flex flex-col`}>
<div className="p-4 border-b border-slate-700">
<h1 className="text-xl font-bold">Dashboard</h1>
</div>
<nav className="flex-1 p-4 space-y-2">
<NavLink icon="📊" label="Overview" />
<NavLink icon="📈" label="Analytics" />
<NavLink icon="👥" label="Users" />
<NavLink icon="⚙️" label="Settings" />
</nav>
<div className="p-4 border-t border-slate-700">
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="text-sm text-slate-400 hover:text-slate-200"
>
{sidebarOpen ? 'Collapse' : 'Expand'}
</button>
</div>
</aside>
{/* Main Content */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Header */}
<header className="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<h2 className="text-2xl font-semibold text-gray-900">Overview</h2>
<button className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">
Sign Out
</button>
</header>
{/* Dashboard Grid */}
<main className="flex-1 overflow-auto p-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<DashboardCard title="Total Revenue" value="$45,231" trend="+12%" />
<DashboardCard title="Active Users" value="1,234" trend="+8%" />
<DashboardCard title="Conversion Rate" value="3.45%" trend="-2%" />
<DashboardCard title="Churn Rate" value="0.89%" trend="-1%" />
</div>
</main>
</div>
</div>
);
}
interface NavLinkProps {
icon: string;
label: string;
}
function NavLink({ icon, label }: NavLinkProps) {
return (
<button className="flex items-center space-x-3 w-full px-4 py-2 rounded-lg hover:bg-slate-800 transition-colors">
<span className="text-lg">{icon}</span>
<span className="text-sm font-medium">{label}</span>
</button>
);
}
interface DashboardCardProps {
title: string;
value: string;
trend: string;
}
function DashboardCard({ title, value, trend }: DashboardCardProps) {
const isPositive = !trend.startsWith('-');
return (
<div className="bg-white rounded-lg shadow p-6 border-l-4 border-blue-500">
<p className="text-gray-600 text-sm font-medium">{title}</p>
<p className="text-3xl font-bold text-gray-900 mt-2">{value}</p>
<p className={`text-sm font-semibold mt-2 ${isPositive ? 'text-green-600' : 'text-red-600'}`}>
{trend} from last month
</p>
</div>
);
}
export default App;
This layout uses Flexbox for responsiveness. The sidebar toggles width, the header stays fixed, and the main content area scrolls independently. The DashboardCard component demonstrates reusability—you'll create dozens of variants like this.
Step 4: Organize Your Component Structure
Create a folder structure to keep components organized as your dashboard grows:
src/
├── components/
│ ├── Sidebar.tsx
│ ├── Header.tsx
│ ├── DashboardCard.tsx
│ └── Navigation.tsx
├── pages/
│ ├── Dashboard.tsx
│ └── Settings.tsx
├── hooks/
│ └── useSidebar.ts
├── App.tsx
├── index.css
└── main.tsx
Extract Sidebar and Header into separate files to practice component composition. For example, src/components/Sidebar.tsx:
interface SidebarProps {
isOpen: boolean;
onToggle: () => void;
}
export function Sidebar({ isOpen, onToggle }: SidebarProps) {
return (
<aside className={`${isOpen ? 'w-64' : 'w-20'} bg-slate-900 text-white transition-all duration-300 flex flex-col`}>
{/* Sidebar content */}
</aside>
);
}
This modular approach makes it easy to test, reuse, and maintain components as your dashboard scales.
Why These Tools Matter
Vite offers sub-100ms hot module replacement (HMR), meaning code changes appear instantly without a full browser reload. TypeScript catches component prop errors at edit time, not runtime. Tailwind CSS eliminates style conflicts because class names are generated, not hand-written.
Key Takeaways
- Vite scaffolds a modern React project with zero configuration needed for development.
- Tailwind's content purge (via
contentintailwind.config.js) ensures CSS stays under 50 KB for a basic dashboard. - Split components into files early—
Sidebar,Header,Card—to reduce merge conflicts in team projects. - Use TypeScript interfaces for props (
SidebarProps,DashboardCardProps) to document expectations and catch bugs. - A responsive grid (e.g.,
grid-cols-1 md:grid-cols-2 lg:grid-cols-4) adapts to mobile, tablet, and desktop automatically.
Frequently Asked Questions
What's the difference between npm and pnpm?
Both are package managers for Node.js. pnpm uses a different storage model and is 25% faster on large projects (2026 benchmarks). For SaaS dashboards under 100 dependencies, the difference is negligible. Use whichever your team prefers.
Do I need to eject from Vite?
No. Vite's config file (vite.config.ts) is minimal and extensible. Most SaaS dashboards never need to eject. If you do, you're likely adding server-side rendering (SSR), which is covered in a later article.
Can I use CSS Modules instead of Tailwind?
Yes, but Tailwind is more common in 2026 SaaS projects because it reduces cognitive load (no naming) and integrates with UI component libraries like Shadcn/ui. If your team uses CSS Modules, the structure remains identical.
How do I handle dark mode in Tailwind?
Tailwind supports dark mode with the dark: prefix. Enable it in tailwind.config.js: darkMode: 'class'. Then use <div className="bg-white dark:bg-gray-900">. Toggle by adding/removing the dark class on the root <html> element.
What's the production build size?
A basic dashboard with Vite and Tailwind ships about 35–40 KB gzipped (JavaScript + CSS). Add React Router (+8 KB), React Query (+5 KB), and a charting library (+50 KB) and you're still under 100 KB—well within performance budgets.