Rust CSS Engine

bext includes a Rust-native Tailwind CSS v4 compiler powered by encre-css. It replaces the TypeScript Tailwind pipeline with a pure-Rust implementation that runs inside the server process — no Node.js or Bun subprocess needed for CSS.

Why

The traditional Tailwind build pipeline requires spawning Bun to run the tailwindcss npm package. This works but adds latency to builds, requires a JS runtime for CSS, and makes CSS-only changes trigger full bundle rebuilds.

The Rust CSS engine eliminates all of that:

TypeScript Tailwind Rust CSS Engine
CSS build time ~200-500ms (Bun subprocess) ~1-30ms (in-process)
CSS-only file change Full Bun rebuild Rust rebuild, no Bun
Per-route CSS Not available Automatic (inline <style>)
Runtime dependency Bun + tailwindcss npm None (compiled into binary)
Dev mode hot reload File watcher + Bun File watcher + Rust

Enabling

The route-css feature is included in default builds. No configuration needed — it works automatically.

To verify it's compiled in:

bext version
# Look for "route-css" in the feature flags

To explicitly disable it:

cargo build -p bext-server --release \
  --no-default-features \
  --features v8,tls,nginx-compat

How it works

At startup

When bext initializes a PRISM site, it builds public/styles.css from Rust before the render pool starts:

PRISM init → bext_css::build_site_css() → scan src/**/*.{tsx,ts,jsx,css,html}
           → extract class candidates → encre-css generate → write public/styles.css

The TypeScript build script automatically skips its own Tailwind step (via BEXT_RUST_CSS=1 env var).

Per-route CSS

After every SSR render, bext scans the rendered HTML for class="..." attributes and generates a <style data-bext-css> block containing only the Tailwind utilities used on that specific page. This is injected before </head>.

<!-- Before: generic styles.css with everything -->
<link rel="stylesheet" href="/styles.css" />

<!-- After: also includes per-route minimal CSS -->
<link rel="stylesheet" href="/styles.css" />
<style data-bext-css>
  .flex { display: flex; }
  .items-center { align-items: center; }
  .gap-4 { gap: 1rem; }
  /* only classes used on THIS page */
</style>

This works on both rendering paths:

- On-demand mode: after renderToString() returns HTML

- Pre-built mode: after __fetch returns an HTML response

CSS-only live reload

When the file watcher detects that only .css files changed (no .ts/.tsx), it takes a fast path:

1. Skips Bun entirely 2. Calls bext_css::build_site_css() in Rust (~1ms) 3. Clears the ISR response cache 4. Sends HMR css-update to connected browsers

In dev mode, the HMR client hot-swaps both <link> stylesheets and inline <style data-bext-css> blocks without a page reload.

CLI

Build CSS for one site

bext css sites/cloud

Build CSS for all sites

bext css sites/ --all

Output:

Building CSS for all sites in sites/
  main                 12.4 KB
  registry              8.6 KB
  learn                 8.4 KB
  status                8.4 KB
  companion-site        8.3 KB
  blog                  8.1 KB
  docs                  7.6 KB
  cloud                 5.9 KB

  8 sites, 66.8 KB total, 280ms

Build with bext build

CSS is built automatically before the JS bundle:

bext build
#   css        12.4 KB (Rust)
#   Build complete in 1200ms (bundle: .bext/production.js)

Skip CSS if you want to use an existing styles.css:

bext build --no-css

Theme

The bext design tokens are compiled into the engine:

Colors

Token Value Usage
bext-black #0a0a0a bg-bext-black, text-bext-black
bext-white #fafafa bg-bext-white, text-bext-white
bext-accent-2 #10b981 bg-bext-accent-2, text-bext-accent-2
bext-error #ef4444 text-bext-error, border-bext-error
bext-warning #f59e0b text-bext-warning
bext-success #10b981 text-bext-success
bext-info #3b82f6 text-bext-info
bext-cloud #8b5cf6 Site accent: Cloud dashboard
bext-registry #f59e0b Site accent: Plugin registry
bext-learn #10b981 Site accent: Learn platform
bext-companion #6366f1 Site accent: Companion app

Fonts

- Sans: "Inter", system-ui, -apple-system, sans-serif

- Mono: "JetBrains Mono", "Fira Code", monospace

Dark mode

Class-based dark mode using .dark selector:

<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
  Adapts to dark mode
</div>

Responsive breakpoints

All standard Tailwind v4 breakpoints are available:

<div className="hidden md:flex lg:grid lg:grid-cols-3">
  Responsive layout
</div>

CSS custom properties

The engine also outputs CSS custom properties (design tokens) for use in hand-written CSS:

:root {
  --bext-black: #0a0a0a;
  --bext-white: #fafafa;
  --bext-accent: #3b82f6;
  --bg: var(--bext-white);
  --text: var(--bext-black);
  --border: #e5e7eb;
  --surface: #f9fafb;
}
:root.dark {
  --bg: #111827;
  --text: #f3f4f6;
  --border: #374151;
  --surface: #1f2937;
}

Use them in component styles:

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
}

Architecture

The CSS engine lives in crates/bext-css/ and depends on encre-css (MIT, Tailwind v4 compatible). Key functions:

Function Purpose
bext_css::config() Shared theme config (initialized once via OnceLock)
bext_css::build_site_css(path) Build public/styles.css for a site
bext_css::build_all_sites(path) Batch build for all sites in a directory
bext_css::inject_route_css(html) Extract classes from HTML, inject <style>
bext_css::css_for_html(html) Generate CSS for rendered HTML (no injection)
bext_css::is_css_only_change(files) Classify file changes for the watcher fast path
bext_css::rebuild_if_changed(path) Mtime-aware rebuild (skip if up-to-date)
bext_css::content_hash(css) FNV-1a hash for cache busting

Compatibility with TypeScript Tailwind

Both engines can coexist. When the Rust engine is active, it sets BEXT_RUST_CSS=1 before running Bun build scripts. The TypeScript buildCSS() and ensureCSS() functions check this variable and skip their work:

// In build.ts / serve.ts — automatically skipped
if (process.env.BEXT_RUST_CSS === "1") {
  console.log("css (handled by bext-server Rust engine)");
  return;
}

To force the TypeScript pipeline (e.g., for debugging), unset the variable:

BEXT_RUST_CSS=0 bext build