Tailwind integration
Use Tailwind CSS utilities with BTSX class attributes through the selected bundler’s adapter.
Beast normalizes class to className and merges attribute values, so Tailwind utilities work natively. The creator can configure Tailwind through the selected Vite, Rspack, or Rsbuild adapter; choosing shadcn enables it automatically.
For consistency, this guide uses className="..." for Tailwind utilities. class="..." is also accepted and normalized to className. Do not use selector shorthands for utilities (element.tw-class) — use element(className="...").
| BTSX | TSRX output |
|---|---|
| div(className="flex bg-white p-4") | <div className="flex bg-white p-4"> |
| div(className="card shadow p-6") | <div className="card shadow p-6"> |
| Button(className="px-4 py-2" variant="primary") | <Button className="px-4 py-2" variant="primary"> |
| section#hero(className="hero min-h-screen") | <section id="hero" className="hero min-h-screen"> |
Add Tailwind v4 and the PostCSS bridge. tailwind-merge (or cn helper) is optional but recommended for conditional class merging.
bun add -d tailwindcss @tailwindcss/postcssbun add tailwind-merge # optional, for cn helperTailwind v4 needs no tailwind.config.js by default. Wire PostCSS and import Tailwind in your CSS entry.
const config = { plugins: { "@tailwindcss/postcss": {}, },};export default config;@import 'tailwindcss'; /* your base overrides remain below */:root { color-scheme: light dark; }body { margin: 0; }No Tailwind-specific Vite plugin is required. PostCSS runs before Beast/Octane, so the order is PostCSS (Tailwind) → Beast (BTSX→TSRX) → Octane.
import { defineConfig } from "vite";import { beastOctane } from "beast-tsrx/vite"; export default defineConfig({ plugins: [beastOctane()],});Write utilities with className="..." ( class="..." also works and is normalized to className). Do not use selector shorthands like element.tw-class — use element(className="...").
props { title, active }: { title: string; active?: boolean } article(className="bg-white shadow rounded-xl p-6" className={active ? "ring-2 ring-blue-500" : ""}) h2(className="text-lg font-semibold") #{title} p(className="text-sm text-zinc-500") Utility classes are just class names. div(className="flex gap-2 mt-4") span(className="inline-flex items-center rounded-full bg-zinc-100 px-2 py-0.5 text-xs") Active a(className="button bg-blue-600 text-white px-4 py-2 rounded" href="#") ActionFor conditional utilities, prefer a cn helper (clsx + tailwind-merge) to deduplicate conflicting classes. It works identically inside BTSX attribute expressions.
import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs));}import { cn } from "./lib/utils.ts"props { variant }: { variant: "default" | "active" } span(className={cn("rounded-full px-3 py-1 text-xs", variant === "active" && "bg-blue-600 text-white")}) #{variant}Scoped <style> and Tailwind utilities can live in the same component. Use <style> for component-scoped non-utility CSS and :global() for escapes — exactly as in the styling golden.
fragment article(className="bg-white p-6 rounded-xl card") h2(className="text-xl font-bold") Panel p(className="text-zinc-600") Scoped style below augments utilities. style .card { border: 1px solid #e5e7eb; } :global(body) { margin: 0; }For an existing Vite project, replace src/style.css with @import 'tailwindcss'; plus your overrides, add postcss.config.mjs, and run the install command above. For a new project, pass --tailwind; shadcn enables Tailwind automatically.
- Keep
beastOctane()as the only Vite plugin - Restart
bun run devafter adding PostCSS config - Verify with
bun run build— Tailwind is compiled via PostCSS before Octane