# How to Configure Topcoat with Tailwind CSS Integration

> Learn how to configure Topcoat with Tailwind CSS integration. Topcoat bundles a Tailwind CSS CLI wrapper that compiles styles at build time, eliminating Node.js.

- Repository: [Tokio/topcoat](https://github.com/tokio-rs/topcoat)
- Tags: how-to-guide
- Published: 2026-07-31

---

**Topcoat bundles a standalone Tailwind CSS CLI wrapper that compiles your styles at build time via a Cargo build script, eliminating the need for Node.js or external asset pipelines.**

Configuring Topcoat with Tailwind CSS integration allows you to generate optimized stylesheets directly within Rust's build process. The `tokio-rs/topcoat` crate provides this functionality through an opt-in `tailwind` feature that downloads the Tailwind CLI, scans your `view!` macros for utility classes, and serves the resulting CSS as a content-hashed asset. This approach keeps your build entirely self-contained while providing full access to Tailwind's just-in-time compiler.

## How the Tailwind Integration Works

### Architecture Overview

The integration centers on the `topcoat::tailwind` module, implemented in [[`crates/topcoat/src/tailwind.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/src/tailwind.rs)](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/src/tailwind.rs). When enabled, Topcoat acts as a thin wrapper around the official Tailwind CLI, handling binary management and asset pipeline integration automatically.

The system relies on three core components:

- **Feature-gated compilation** – The `tailwind` Cargo feature must be enabled for both runtime and build dependencies
- **Build script execution** – `BuildConfig::new().render()` orchestrates CLI download, CSS generation, and file output
- **Asset macro expansion** – `tailwind::stylesheet!()` resolves to `topcoat::asset::asset!(concat!(env!("OUT_DIR"), "/tailwind.css"))`, integrating with Topcoat's existing asset serving infrastructure

### Build-Time Execution Flow

When you invoke `BuildConfig::new().render()` in your [`build.rs`](https://github.com/tokio-rs/topcoat/blob/main/build.rs), the following occurs:

1. **Binary acquisition** – If not present in `OUT_DIR`, the build script downloads the Tailwind CLI (pinned to version **4.3.2**) for your target platform
2. **Input generation** – Creates a minimal CSS file containing `@import "tailwindcss"` (or uses your custom input file)
3. **Class extraction** – Executes `tailwindcss -i <input> -o $OUT_DIR/tailwind.css --cwd $CARGO_MANIFEST_DIR --minify`, which scans Rust source files for literal `class="..."` values inside `view!` macros
4. **Asset registration** – The generated CSS becomes available via the `stylesheet!()` macro, receiving automatic content hashing and cache-busting headers

The scanning process respects your `.gitignore` file, automatically excluding `target/` and other build artifacts from analysis. Notably, the implementation does **not** parse `view!` macros itself; it delegates class detection entirely to Tailwind's native CLI.

## Step-by-Step Configuration

### Enable the Tailwind Feature

Add the `tailwind` feature to both your runtime dependencies and build dependencies in [`Cargo.toml`](https://github.com/tokio-rs/topcoat/blob/main/Cargo.toml):

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

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

```

The build dependency disables default features to minimize compile times while retaining Tailwind functionality.

### Create the Build Script

Create a [`build.rs`](https://github.com/tokio-rs/topcoat/blob/main/build.rs) file in your crate root that invokes the build configuration:

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

```

This default configuration downloads the Tailwind CLI if necessary, generates an input file containing `@import "tailwindcss"`, and writes the compiled output to `$OUT_DIR/tailwind.css`.

### Link the Stylesheet in Your Layout

Use the `tailwind::stylesheet!()` macro inside your layout's HTML head:

```rust
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 expands to a call to `topcoat::asset::asset!()`, ensuring the stylesheet receives a content-hashed URL and proper cache headers.

## Customization Options

### Custom Input CSS

To use custom Tailwind configurations or import additional CSS, provide a path to your own input file:

```rust
fn main() {
    topcoat::tailwind::BuildConfig::new()
        .input("src/styles/app.css")   // Path relative to Cargo.toml
        .render()
        .expect("Tailwind build failed");
}

```

Create [`src/styles/app.css`](https://github.com/tokio-rs/topcoat/blob/main/src/styles/app.css) with your custom directives:

```css
@import "tailwindcss";

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

```

### Using a Pre-Installed Binary

For offline builds or specific Tailwind versions already installed on your system, bypass the automatic download:

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

```

Alternatively, read the executable path from an environment variable:

```rust
fn main() {
    topcoat::tailwind::BuildConfig::new()
        .executable_env("TAILWIND_CLI")   // Reads path from env var
        .render()
        .expect("Tailwind build failed");
}

```

## Summary

- **Enable the `tailwind` feature** in both `[dependencies]` and `[build-dependencies]` within [`Cargo.toml`](https://github.com/tokio-rs/topcoat/blob/main/Cargo.toml)
- **Create a [`build.rs`](https://github.com/tokio-rs/topcoat/blob/main/build.rs) file** calling `topcoat::tailwind::BuildConfig::new().render()` to trigger CSS generation during compilation
- **Include the stylesheet** via `tailwind::stylesheet!()` in your layout's `<head>` section
- **Store generated CSS** in `$OUT_DIR/tailwind.css`, automatically scanned for utility classes found in `view!` macros
- **Customize behavior** by providing custom input CSS files, specifying alternative CLI binaries, or pinning specific Tailwind versions via the `BuildConfig` API

## Frequently Asked Questions

### Do I need Node.js installed to use Tailwind with Topcoat?

No. Topcoat's integration downloads and executes the standalone Tailwind CSS CLI binary (version 4.3.2 by default) automatically during the Cargo build process. This binary operates independently of Node.js, npm, or any JavaScript runtime, making the build pipeline entirely Rust-native.

### How does Topcoat detect Tailwind classes in my Rust code?

Class detection relies entirely on the Tailwind CLI's built-in scanner, not Topcoat's macro parser. The CLI scans all files in your crate root (respecting `.gitignore`) for literal `class="..."` attributes, including those inside Topcoat's `view!` macro invocations. This ensures compatibility with standard Tailwind purging behavior while keeping the integration lightweight.

### Can I use a specific version of the Tailwind CLI?

Yes. While the default implementation pins version 4.3.2, you can specify an alternative version using the `.version()` method on `BuildConfig`, or provide a pre-installed binary path via `.executable()` or `.executable_env()`. Note that you must ensure version compatibility with your [`tailwind.config.js`](https://github.com/tokio-rs/topcoat/blob/main/tailwind.config.js) or CSS theme directives.

### Why isn't my CSS regenerating when I change my markup?

`BuildConfig::render()` does not emit explicit `cargo:rerun-if-*` directives, relying instead on Cargo's default behavior. The build script reruns whenever any non-ignored file in your package changes. If you are modifying files outside the standard source tree or if your `.gitignore` excludes relevant template files, Cargo may not detect the change. Ensure your markup files are part of the package and not excluded by ignore patterns.