Using Vite
Alpha (0.1) — this is the only install path. Stay on the npm
alphatag until 1.0.latestis 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.
npm create vite@latest my-project -- --template react-ts
cd my-project02 — Install DriftCSS
pnpm add -D @driftcss/vite@alpha@driftcss/core (the native extractor) comes along as a dependency. Prebuilt binaries ship for common platforms. No Rust toolchain.
03 — Configure the Vite plugin
Put driftcss() before the React plugin so it sees the authored className strings.
// 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(),
],
})The plugin injects atom CSS into index.html. You do not import virtual:driftcss in your entry. Set inject: false only for library builds with no HTML.
strict: true fails the build on class names that do not appear in your CSS at all. 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. Recommended in CI.
On first pnpm dev 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 — Start the dev server
pnpm devImport your authored CSS so Vite sees it (and so HMR watches the file if you are not using css globs):
// src/main.tsx
import './button.css'05 — Write a semantic class
/* src/button.css */
.btn {
padding: 0.75rem 1.15rem;
border-radius: 0.5rem;
background: #e8a55c;
color: #1a1102;
}<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.
06 — Import the typed map (optional, recommended)
virtual:driftcss/classes is a default-export object: each semantic name maps to its atom string. Autocomplete works. A typo is a type error.
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:
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.
Optional next
| Vite plugin options | css globs, purge, dts, inject |
| CSS scoping | Compound selectors break after names leave the DOM |
| Alpha scope | What extract does and does not atomize |
npx driftcss init writes driftcss.config.mjs. The Vite plugin loads it (inline options win). CLI check / extract / watch / types do not — pass globs on the command line.
The settings UI in apps/app is a fuller Vite + React demo (clone this repo to run it).