How to Configure Tailwind CSS Integration in Topcoat: A Complete Guide

Topcoat bundles a standalone Tailwind CSS CLI wrapper that runs from a Cargo build script, eliminating the need for Node.js, PostCSS, or a separate asset pipeline while automatically scanning your Rust source files for utility classes.

Topcoat is a Rust web framework that simplifies frontend development by integrating Tailwind CSS directly into the Cargo build process. This guide explains how to configure Tailwind CSS integration in Topcoat using the topcoat::tailwind module, which handles CLI management, CSS generation, and asset hashing entirely at compile time.

How the Tailwind Integration Works

The integration relies on a build-time code generation pattern implemented in crates/topcoat/src/tailwind.rs. When configured, Topcoat downloads the Tailwind CLI binary (pinned to version 4.3.2 by default), scans your Rust source files for class="..." attributes inside view! macros, and emits a minified CSS file into OUT_DIR that is automatically served as a content-hashed asset.

Feature-Gated Architecture

The Tailwind functionality is opt-in via Cargo features. You must enable the tailwind feature for both the runtime dependency (to access the tailwind::stylesheet!() macro) and the build dependency (to run the CLI wrapper):

  • Runtime dependency: Provides the tailwind::stylesheet!() macro that expands to topcoat::asset::asset!(concat!(env!("OUT_DIR"), "/tailwind.css"))
  • Build dependency: Provides BuildConfig for downloading the CLI and executing the build

Build-Time CSS Generation

During compilation, BuildConfig::new().render() performs three operations:

  1. Downloads the Tailwind CLI binary into OUT_DIR if not already present
  2. Generates an input CSS file containing @import "tailwindcss" (or uses your custom input)
  3. Executes tailwindcss -i <input> -o <output> --cwd <cwd> --minify, scanning the crate root while respecting .gitignore to exclude target/ and other artifacts

The build script does not emit cargo:rerun-if-* directives, relying on Cargo's default behavior to rerun when any non-ignored source file changes.

Step-by-Step Configuration

Follow these steps to enable Tailwind CSS in your Topcoat application.

1. Enable the Tailwind Feature in Cargo.toml

Add the tailwind feature to both your regular dependencies and build dependencies:

[dependencies]
topcoat = { version = "0.3.1", features = ["tailwind"] }

[build-dependencies]
topcoat = { version = "0.3.1", default-features = false, features = ["tailwind"] }

Disabling default features for the build dependency minimizes compile times for the build script.

2. Create a Build Script (build.rs)

Create a build.rs file in your crate root that invokes the Tailwind build configuration:

fn main() {
    // Generate Tailwind CSS at compile time
    topcoat::tailwind::BuildConfig::new()
        .render()
        .expect("Tailwind build failed");
}

This default configuration downloads the Tailwind CLI, creates an input file containing @import "tailwindcss", and writes the compiled tailwind.css to $OUT_DIR.

3. Include the Stylesheet in Your Layout

Use the tailwind::stylesheet!() macro inside your layout component to embed the generated CSS:

use topcoat::{
    router::{Slot, layout},
    tailwind,
    view::view,
};

#[layout]
async fn layout(slot: Slot<'_>) -> topcoat::Result {
    view! {
        <!DOCTYPE html>
        <html>
            <head>
                <link rel="stylesheet" href=(tailwind::stylesheet!())>
            </head>
            <body>
                (slot.await?)
            </body>
        </html>
    }
}

The macro resolves to the content-hashed asset URL generated by Topcoat's asset pipeline.

Customization Options

The BuildConfig API in crates/topcoat/src/tailwind.rs supports several customization scenarios.

Using a Custom Input CSS File

To extend Tailwind with custom theme variables or plugins, provide a path to your own CSS file:

Create src/styles/app.css:

@import "tailwindcss";

@theme {
  --font-sans: Inter, sans-serif;
}

Update build.rs to use this input:

fn main() {
    topcoat::tailwind::BuildConfig::new()
        .input("src/styles/app.css")   // custom input path
        .render()
        .expect("Tailwind build failed");
}

Offline Builds with Pre-installed Binaries

For air-gapped or reproducible builds, use a pre-installed Tailwind binary instead of downloading one:

fn main() {
    topcoat::tailwind::BuildConfig::new()
        .executable("tailwindcss")        // assumes binary is on $PATH
        .render()
        .expect("Tailwind build failed");
}

Alternatively, read the path from an environment variable:

fn main() {
    topcoat::tailwind::BuildConfig::new()
        .executable_env("TAILWIND_CLI")
        .render()
        .expect("Tailwind build failed");
}

You can also pin a specific CLI version using .version("4.4.0") if you require a different release than the default 4.3.2.

Summary

  • Enable the tailwind feature in both [dependencies] and [build-dependencies] in your Cargo.toml
  • Call BuildConfig::new().render() in your build.rs to download the CLI and generate CSS during compilation
  • Use tailwind::stylesheet!() in your layout templates to reference the compiled, hashed CSS file
  • Customize the build by providing custom input CSS, specifying offline binaries, or pinning specific CLI versions
  • Rely on automatic rebuilding when Rust source files change, as the integration respects .gitignore and scans view! macros for class names

Frequently Asked Questions

Does Topcoat require Node.js or PostCSS for Tailwind CSS?

No. Topcoat uses a standalone Rust-based wrapper around the Tailwind CSS CLI that downloads and executes the binary directly during Cargo builds. This eliminates the need for Node.js, npm, PostCSS, or any JavaScript-based build pipeline, as implemented in crates/topcoat/src/tailwind.rs.

How does Topcoat detect which Tailwind classes to include?

The Tailwind CLI performs the class-name extraction by scanning your Rust source files for literal class="..." values inside view! macros. Topcoat does not parse the macros itself; instead, it delegates to the official Tailwind CLI with --cwd set to your crate root, which respects .gitignore patterns to avoid scanning target/ and other build artifacts.

Can I use a specific version of the Tailwind CLI?

Yes. The BuildConfig API provides a .version() method to pin a specific release. By default, Topcoat pins version 4.3.2, but you can override this in your build.rs by calling .version("4.4.0") or another supported version before invoking .render().

Where is the generated CSS file stored?

The compiled CSS is written to $OUT_DIR/tailwind.css during the build process. The tailwind::stylesheet!() macro expands to topcoat::asset::asset!(concat!(env!("OUT_DIR"), "/tailwind.css")), which integrates with Topcoat's asset pipeline to provide content-hashed URLs and automatic cache invalidation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →