Skip to content

How it works

Mental model

  1. Authoring — Semantic classes in CSS (.card { … }) and static className / cn() / clsx() in JSX.
  2. Extraction (Rust) — Lightning CSS parses the sheet; shorthands expand to longhands; each declaration becomes a hashed atom (same property + value + context → same class).
  3. Bundler (Vite) — CSS is collected from css globs (and/or the Vite graph when globs are empty), extracted, and injected as virtual:driftcss. JSX is rewritten via resolveClasses using rule order (rule_index).
  4. Runtime — No CSS parser in the browser; only class strings change.

Pipeline

mermaid
flowchart LR
  subgraph inputs [Inputs]
    CSS[Author CSS]
    TSX[TSX/JSX]
  end
  subgraph vite ["@driftcss/vite"]
    Collect[Collect CSS]
    Rewrite[JSX rewrite]
  end
  subgraph rust [Rust core]
    Parse[Lightning CSS parse]
    Extract[Extract atoms + mapping]
    Merge[Merge resolver]
  end
  subgraph outputs [Build outputs]
    AtomsCSS[virtual:driftcss]
    Map["driftcss-map.json on vite build"]
    TSXOut[Rewritten classNames]
  end
  CSS --> Collect --> Parse --> Extract
  Extract --> AtomsCSS
  Extract --> Map
  Extract --> Merge
  TSX --> Rewrite --> Merge --> TSXOut

Dev extracts each CSS file (disk-cached) and merges the payloads. Production compact names (c-0, c-1, …) run one extract on the merged sheet so ids stay unique.

Example

.border { border-color: red; border-style: solid; } expands to side longhands and becomes multiple c-* atoms. .box with the same border-top-color: red reuses the same atom in CSS and HTML.

Pass-through rules

Not every rule is atomized. Global selectors (body {}) and combinators are serialized in source order. Top-level @media { .x { } } is atomized. @layer / @starting-style serialize the block — classes inside are not atomized. See Coverage.

Interactive demo

The home page has a live extract model: edit CSS, see expanded atoms and the rewritten className. It is a client-side illustration (flat selectors only) — nested / combinator CSS will not match a real extractFull run. The Rust compiler runs at build time.

Install path: Using Vite. You can also run the example app from a clone of this repo:

bash
pnpm build:example