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 dev works
  • bg-surface / text-ink colors show
  • Font from Google Fonts loads

Next: build ThemeToggle and Navbar.