---
title: "Tailwind CSS v4 and Vite: Compiling Utilities and Variables"
description: Check ChatCrystal’s two-package installation, Vite plugin, and CSS variable themes while separating build-time utilities from runtime values.
image: "https://zengliangyi.com/og/blog/en/tailwind-v4-vite-setup.png"
---

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](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/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](https://tailwindcss.com/docs/installation/using-vite) 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:



```bash
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.



```javascript
// vite.config.mjs  
  import   { defineConfig }   from   'vite'  ;  
  import   tailwindcss   from   '@tailwindcss/vite'  ;  
  
  export   default   defineConfig  ({ plugins: [  tailwindcss  ()] });
```





```html
<!-- 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  >
```





```css
/* 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](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/client/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](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/client/src/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](https://tailwindcss.com/docs/functions-and-directives) 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](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/client/src/providers/ThemeProvider.tsx) 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](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/client/src/themes/theme.types.ts) 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](https://tailwindcss.com/docs/detecting-classes-in-source-files). 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](/en/blog/electron-app-basics).

Share:

##  Related Posts

[##  AI Conversation Formats: JSONL Events vs SQLite Snapshots

Compare 5 AI adapter formats: separate JSONL events from SQLite keys, and inspect indexed, orphan, and unknown-version chat transcripts in memory stores.          曾亮懿       October 7, 2026      4 min read

#ChatCrystal

#Node.js

#SQLite](/en/blog/ai-conversation-format-divergence)    [##  How Claude Code, Cursor, Copilot, and Trae Store Chats on Disk

Compare Claude Code, Cursor, Copilot, and Trae storage paths. Distinguish raw copies from exports, and verify migration retention with test manifests.          曾亮懿       October 7, 2026      4 min read

#ChatCrystal

#Node.js

#SQLite](/en/blog/ai-conversation-management-options)    [##  Five AI conversation parsers: retained fields and loss boundaries

Build an acceptance matrix from Claude Code, Codex, Cursor, Trae, and Copilot parsers, checking tool flags, thinking, order, and snapshot coverage.          曾亮懿       October 7, 2026      4 min read

#ChatCrystal

#TypeScript

#Node.js](/en/blog/ai-conversation-parser-boundaries)

##  From writing to code

Explore ChatCrystal, developer tools and daily learning notes on my GitHub. Questions about the implementation are welcome.

[Visit my GitHub](https://github.com/ZengLiangYi)

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "ZengLiangYi Blog",
  "alternateName": [
    "Zeng Liangyi",
    "ZengLiangYi",
    "zengliangyi.com"
  ],
  "url": "https://zengliangyi.com",
  "description": "曾亮懿的个人网站。记录 AI Native 探索、开发者工具与全栈产品实践。"
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Tailwind CSS v4 and Vite: Compiling Utilities and Variables",
  "description": "Check ChatCrystal’s two-package installation, Vite plugin, and CSS variable themes while separating build-time utilities from runtime values.",
  "url": "https://zengliangyi.com/en/blog/tailwind-v4-vite-setup/",
  "image": "https://zengliangyi.com/images/blog/tailwind-v4-vite-setup.svg",
  "datePublished": "2026-10-04T00:00:00.000Z",
  "dateModified": "2026-10-23T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "曾亮懿",
    "url": "https://zengliangyi.com/en/about"
  },
  "publisher": {
    "@type": "Organization",
    "name": "ZengLiangYi Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://zengliangyi.com/favicon-96x96.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://zengliangyi.com/en/blog/tailwind-v4-vite-setup/"
  }
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://zengliangyi.com/en"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog",
      "item": "https://zengliangyi.com/en/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Tailwind CSS v4 and Vite: Compiling Utilities and Variables",
      "item": "https://zengliangyi.com/en/blog/tailwind-v4-vite-setup/"
    }
  ]
}
```
