Skip to content

Using Vite

Alpha (0.1) — this is the only install path. Stay on the npm alpha tag until 1.0. latest is not published.

DriftCSS works by reading your CSS, hashing each declaration into an atom, and rewriting static className / cn() / clsx() in JSX. Zero runtime style engine. You write ordinary semantic CSS.

Installation

01 — Create your project

Start a Vite + React + TypeScript app if you do not have one.

bash
npm create vite@latest my-project -- --template react-ts
cd my-project
pnpm install

02 — Initialize DriftCSS

bash
npx driftcss@alpha init

That installs @driftcss/vite@alpha and adds the plugin to vite.config. @driftcss/core (the native extractor) comes along with prebuilt binaries. No Rust toolchain.

The plugin injects atom CSS into index.html. You do not import virtual:driftcss in your entry. strict: true is on by default from init. Nested or grouped selectors (the default Vite starter) stay as pass-through CSS; those names are still known. A typo like className="btnn" still fails.

If vite.config is not a normal plugins: [] file, init prints a snippet and leaves the file alone — use manual setup below.

03 — Start the dev server

bash
pnpm dev

On first run the plugin writes src/driftcss-env.d.ts so tsc -b sees the virtual modules (Vite 8 templates include only src). No tsconfig types edit.

04 — Write a semantic class

css
/* src/button.css */
.btn {
  padding: 0.75rem 1.15rem;
  border-radius: 0.5rem;
  background: #e8a55c;
  color: #1a1102;
}
tsx
<button className="btn">Save</button>

In development the class names are content hashes (c-a1b2c3d4). Production uses compact ids (c-0, c-1, …) after shorthand expansion.

virtual:driftcss/classes is a default-export object: each semantic name maps to its atom string. Autocomplete works. A typo is a type error.

tsx
import styles from 'virtual:driftcss/classes'

export function Save() {
  return <button className={styles.btn}>Save</button>
}

Those values are already atoms at runtime, so the JSX transform leaves styles.btn alone. Fold or merge with cn() / clsx() the same way:

tsx
import clsx from 'clsx'
import styles from 'virtual:driftcss/classes'

<button className={clsx(styles.btn, on && styles.btnGhost)} />

In dev the plugin writes src/driftcss.d.ts (dts defaults on; it keeps a root driftcss.d.ts if you already have one). Vite 8's include: ["src"] picks the src/ file up. DriftClassName is a global union of your semantic names — you can type className?: DriftClassName without importing anything.

That is the whole golden path. Next, Vue, Nuxt, and the rest are not this alpha.


Manual setup

Use this if you would rather not run init, or if init could not patch your Vite config.

bash
pnpm add -D @driftcss/vite@alpha

Put driftcss() before the React plugin so it sees the authored className strings.

ts
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import driftcss from '@driftcss/vite'

export default defineConfig({
  plugins: [
    driftcss({
      css: ['src/**/*.css'],
      strict: true,
    }),
    react(),
  ],
})

Set inject: false only for library builds with no HTML.

npx driftcss@alpha init --config also writes driftcss.config.mjs (the Vite plugin loads it; inline options win). CLI check / extract / watch / types do not load that file — pass globs on the command line.


Optional next

Vite plugin optionscss globs, purge, dts, inject
CSS scopingCompound selectors break after names leave the DOM
Alpha scopeWhat extract does and does not atomize

The settings UI in apps/app is a fuller Vite + React demo (clone this repo to run it).