ChatCrystal compiles styles with Tailwind CSS v4’s Vite plugin, then injects theme variables through React’s ThemeProvider. The build generates rules for classes; the running application changes variable values. Together, those paths determine the page’s appearance.
Install both packages and provide the entry files
The fixed client/package.json declares both tailwindcss and @tailwindcss/vite, each with the ^4.3.3 version range. Installing only the plugin is an incomplete instruction. v4 also supports PostCSS integration. The Vite plugin is this project’s chosen route, rather than evidence that Tailwind removed PostCSS support. The official Vite installation guide lists both packages.
This is an isolated CSS exercise in an empty directory, not a replacement for the project configuration. Use Node 24 or later:
npm init -y
npm install -D vite@8.3.0 tailwindcss@4.3.3 @tailwindcss/vite@4.3.3
npx vite build
Run the final command after saving these three files.
// vite.config.mjs
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({ plugins: [tailwindcss()] });
<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Token exercise</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<main class="grid grid-cols-1 gap-4 p-4 sm:grid-cols-2">
<div class="bg-panel text-ink rounded-lg p-4">Build classes</div>
<div class="bg-panel text-ink rounded-lg p-4">Change variables</div>
</main>
</body>
</html>
/* style.css */
@import 'tailwindcss';
:root {
--panel: Canvas;
--ink: CanvasText;
}
@utility bg-panel {
background-color: var(--panel);
}
@utility text-ink {
color: var(--ink);
}
After building, open the page with npx vite preview --host 127.0.0.1. Check the single column and the two columns above the sm breakpoint. Change --panel and --ink in developer tools to verify that the same utilities follow the new values. Deliberately remove the plugin and rebuild, checking both the output and whether the utilities work on the page; restore it and check again. A successful exit alone does not prove the Tailwind directives were compiled.
What the real project adds
vite.config.ts registers react() alongside tailwindcss(), defines the @ alias with import.meta.dirname, and builds both the main entry and Electron onboarding. Its development server binds to 127.0.0.1:13721 and proxies /api to localhost:3721. That is a development proxy, rather than a production deployment or cloud authentication configuration.
index.css imports both Tailwind and tw-animate-css, bridges color and radius variables with @theme inline, and defines application utilities. See the directive reference for @theme and @utility. CSS-first configuration does not justify deleting animation packages or compatibility variables that the project still uses.
How theme values reach components
ThemeProvider registers dark-workshop, dawn-haze, and jade-abyss, defaulting to dawn-haze. It restores a versioned localStorage choice, sets root variables in an effect, and approximates background brightness to choose color-scheme. This path does not automatically select a theme from the operating system’s prefers-color-scheme setting.
themeToCSSVars converts bgPrimary to --bg-primary and graphProject1 to --graph-project-1, then adds fonts and radius. Although ThemeDefinition declares density, the conversion function does not emit a density variable. A declared field does not prove its visual effect is implemented.
Keep class names visible to the compiler
Tailwind scans source text for complete class names; it does not execute JavaScript to infer string concatenation. See class detection. Use complete static alternatives for states, and place genuinely dynamic values such as progress in style or CSS variables. Debug by checking generated CSS, assigned variables, final computed styles, and contrast separately.
Layout utilities do not automatically fix every narrow-screen overflow. Theme variables do not guarantee contrast or reduced motion. Check keyboard focus, phone widths, light and dark themes, and prefers-reduced-motion behavior. For the desktop host, also distinguish the page from the main process; see Electron basics.