Integrations

Tailwind integration

Use Tailwind CSS utilities with BTSX class attributes through the selected bundler’s adapter.

source

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="...").

BTSXTSRX 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.

Terminalshell
bun add -d tailwindcss @tailwindcss/postcssbun add tailwind-merge  # optional, for cn helper

Tailwind v4 needs no tailwind.config.js by default. Wire PostCSS and import Tailwind in your CSS entry.

postcss.config.mjsjs
const config = {  plugins: {    "@tailwindcss/postcss": {},  },};export default config;
src/style.csscss
@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.

vite.config.tsts
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="...").

Card.btsxbtsx
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="#") Action

For conditional utilities, prefer a cn helper (clsx + tailwind-merge) to deduplicate conflicting classes. It works identically inside BTSX attribute expressions.

lib/utils.tsts
import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {  return twMerge(clsx(inputs));}
Badge.btsxbtsx
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.

Panel.btsxbtsx
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 dev after adding PostCSS config
  • Verify with bun run build — Tailwind is compiled via PostCSS before Octane