Goal
Create a Vite + React app, install Tailwind CSS v4, and leave a clean page shell ready for the portfolio sections.
1. Create the project
npm create vite@latest
Then:
cd react-tailwind
npm run dev
Open the URL Vite prints (usually http://localhost:5173) and confirm the starter page loads.
2. Install Tailwind CSS v4
npm install tailwindcss @tailwindcss/vite
3. Add the Vite plugin
Open vite.config.js and register Tailwind next to React:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss(), react()],
});
No tailwind.config.js is required to start.
4. Create src/style.css
Replace (or create) your main stylesheet with:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--font-display: "Segoe UI", ui-sans-serif, system-ui, sans-serif;
--font-sans: "DM Sans", ui-sans-serif, system-ui, sans-serif;
--color-brand-50: #eff6ff;
--color-brand-100: #dbeafe;
--color-brand-200: #bfdbfe;
--color-brand-500: #3b82f6;
--color-brand-600: #2563eb;
--color-brand-700: #1d4ed8;
--color-brand-900: #1e3a8a;
--color-ink: #0f172a;
--color-muted: #64748b;
--color-surface: #f8fafc;
}
Tailwind chunks explained
| Chunk | What it does |
|---|---|
@import "tailwindcss" |
Loads Tailwind's reset, theme, and utilities |
@custom-variant dark (...) |
Makes dark: utilities follow a .dark class on <html> (manual toggle later) |
@theme { ... } |
Defines design tokens that become utilities |
--font-sans |
Enables font-sans |
--color-brand-600 |
Enables bg-brand-600, text-brand-600, etc. |
--color-ink / --color-muted / --color-surface |
Enables text-ink, text-muted, bg-surface |
5. Load CSS and a font
In index.html, add the stylesheet and DM Sans (matches --font-sans):
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Portfolio</title>
<link href="./src/style.css" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,700;1,9..40,400&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Optional: add class="scroll-smooth" on <html> for nicer in-page nav jumps later.
6. Page shell in App.jsx
Clear the Vite starter UI and leave a shell for upcoming components:
function App() {
return (
<div className="min-h-screen bg-surface font-sans text-ink antialiased dark:bg-slate-950 dark:text-slate-100">
{/* Navbar */}
<main>
{/* Hero, Skills, Projects, Contact */}
<p className="p-6 text-lg text-muted">Portfolio setup complete.</p>
</main>
{/* Footer */}
</div>
);
}
export default App;
Tailwind chunks explained
| Class | Meaning |
|---|---|
min-h-screen |
At least full viewport height |
bg-surface |
Background from your @theme token |
font-sans |
Font from --font-sans |
text-ink |
Main text color from theme |
antialiased |
Smoother font rendering |
dark:bg-slate-950 |
Background when <html> has class dark |
dark:text-slate-100 |
Light text in dark mode |
p-6 |
Padding on all sides (1.5rem) |
text-lg |
Larger body text |
text-muted |
Softer secondary text from theme |
Save and confirm the page background and text look correct. If styles do not apply, double-check the Vite plugin and the CSS import path.
Checklist
-
npm run devworks -
bg-surface/text-inkcolors show - Font from Google Fonts loads
Next: build ThemeToggle and Navbar.