# How to Implement Conditional CSS Class Lists with Topcoat's class! Macro

> Master Topcoat's class! macro for type-safe CSS class strings. Learn conditional syntax for dynamic class lists, ensuring clean, zero-allocation output. Optimize your web components today.

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

---

**Topcoat's `class!` macro enables type-safe, zero-allocation construction of CSS class strings using conditional syntax like `"active" if is_enabled`, automatically omitting empty values and the entire attribute when no classes are present.**

The `class!` macro in the [tokio-rs/topcoat](https://github.com/tokio-rs/topcoat) repository provides a declarative DSL for assembling HTML class attributes within Rust view templates. By implementing the `ClassViewParts` trait for diverse types including strings, options, vectors, and nested classes, the macro allows developers to compose complex conditional class lists without intermediate string allocations.

## Conditional Syntax Overview

The macro accepts a comma-separated list of entries where each item follows one of three patterns documented in [[`crates/topcoat-view/macro/docs/class.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/macro/docs/class.md)](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/macro/docs/class.md):

- **`expr`** — Always included in the final string.
- **`expr if cond`** — Included only when `cond` evaluates to `true`.
- **`expr if cond else alt`** — Includes `expr` when `cond` is `true`, otherwise includes `alt`.

This syntax allows for expressive, declarative class construction directly inside Topcoat's `view!` macro.

## Core Architecture: Traits and Writers

### The ClassViewParts Trait

According to the implementation in [[`crates/topcoat-view/src/class.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/src/class.rs)](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/src/class.rs), every entry in a `class!` invocation must implement the `ClassViewParts` trait. This trait defines two critical methods:

- `fn is_present(&self) -> bool` — Determines whether the entry contributes to the output. Implementations for `&str`, `String`, `Option<T>`, `Vec<T>`, arrays, tuples, and nested `Class` values all treat empty strings and `None` as absent.
- `fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>)` — Writes the entry into the output buffer when `is_present()` returns `true`.

### The ClassWriter Implementation

The `ClassWriter` struct (lines 56-74 in [`class.rs`](https://github.com/tokio-rs/topcoat/blob/main/class.rs)) manages the actual string construction. It ensures that:
- Present entries are separated by exactly one space.
- Absent entries produce no separator.
- No leading or trailing whitespace appears in the final attribute value.

### Mixed-Type Conditionals with ClassBranch

When using the `if/else` syntax with branches of different types, the macro lowers them to the hidden enum `ClassBranch<A, B>` (lines 23-33 of [`class.rs`](https://github.com/tokio-rs/topcoat/blob/main/class.rs)). This allows mixed-type conditionals without requiring extra heap allocation or boxing, preserving the macro's zero-allocation guarantees.

## Automatic Omission of Empty Classes

The `Class` struct tracks whether any entry is present through the `is_present()` method. When the entire list contains no present entries—such as when all conditionals evaluate to false or all options are `None`—the `attribute_present` method returns `false` (lines 29-33 of [`class.rs`](https://github.com/tokio-rs/topcoat/blob/main/class.rs)). This signals Topcoat to omit the entire `class` attribute from the rendered HTML element rather than emitting `class=""`.

## Practical Code Examples

The following examples demonstrate the full capabilities of the `class!` macro:

```rust
use topcoat::view::{class, view};

#[topcoat::view::component]
async fn example() -> topcoat::Result {
    let is_active = true;
    let variant: Option<&str> = Some("primary");
    let sizes = vec!["px-4".to_owned(), "py-2".to_owned()];
    let enabled = false;

    // Static classes with simple conditional
    view! {
        <button class=(class!("btn", "btn-lg", "active" if is_active))>
            "Save"
        </button>
    }

    // Combining Options, Vectors, and if/else branches
    view! {
        <button class=(class!(
            "btn",
            variant,          // Option<&str>, omitted if None
            sizes,            // Vec<String>, each entry added
            "cursor-pointer" if enabled else "opacity-50"
        ))>
            "Save"
        </button>
    }

    // Nested class composition
    view! {
        <div class=(class!("card", class!("btn", "btn-lg")))>{"Content"}</div>
    }

    // Attribute completely omitted when all entries absent
    view! {
        <p class=(class!(Option::<&str>::None, ""))>{"No class attribute"}</p>
    }
}

```

**Key behaviors illustrated:**
- **Example 1** shows basic conditional inclusion using `"active" if is_active`.
- **Example 2** demonstrates heterogeneous composition: an `Option`, a `Vec`, and a conditional branch are seamlessly combined via the `ClassViewParts` trait.
- **Example 3** illustrates nesting where `class!("card", class!("btn", "btn-lg"))` renders as `class="card btn btn-lg"` with correct spacing handled by `ClassWriter`.
- **Example 4** results in `<p>No class attribute</p>` because both entries are absent, triggering the omission logic in the `Class` attribute implementation.

## Summary

- **Declarative syntax** — Use `expr if cond` and `expr if cond else alt` for conditional classes.
- **Trait-based extensibility** — Any type implementing `ClassViewParts` (strings, Options, Vectors, tuples) works in `class!`.
- **Zero-allocation composition** — `ClassWriter` streams entries directly into the view buffer without intermediate strings.
- **Smart attribute omission** — When all entries are absent, Topcoat omits the `class` attribute entirely rather than rendering an empty value.
- **Mixed-type branches** — The `ClassBranch<A, B>` enum enables `if/else` with different types without boxing.

## Frequently Asked Questions

### How does the `class!` macro handle optional classes?

The macro treats `Option<T>` as an optional entry. When the variant is `Some`, the contained value is included; when `None`, the entry is skipped entirely. This logic is implemented via `ClassViewParts` for `Option<T>` in [`crates/topcoat-view/src/class.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/src/class.rs), where `is_present()` returns `true` only for `Some` variants.

### What happens when all class entries evaluate to absent?

When every entry in the `class!` list is absent—whether through `None` values, empty strings, or false conditionals—the `Class` value reports `attribute_present` as `false`. Topcoat then omits the entire `class` attribute from the rendered HTML element, resulting in cleaner markup without empty attributes.

### Can I use different types in the if and else branches of a conditional?

Yes. The macro supports mixed-type conditionals like `"enabled" if active else "disabled"` where the branches have different types. The implementation uses the `ClassBranch<A, B>` enum to unify the types without requiring allocation, as defined in lines 23-33 of [`class.rs`](https://github.com/tokio-rs/topcoat/blob/main/class.rs).

### How do I combine multiple dynamic class sources?

You can pass any combination of static strings, `Option<T>`, `Vec<T>`, arrays, tuples, and nested `Class` values to the macro. Each type implements `ClassViewParts`, allowing heterogeneous lists like `class!("base", maybe_variant, vec_of_sizes, nested_class)`. The `ClassWriter` ensures proper spacing between all present entries.