# How CSS Modules Integration Works in Dioxus: A Deep Dive into the Manganis Asset System

> Learn how Dioxus integrates CSS Modules with its Manganis asset system. Discover type-safe constants and build-time transformations for efficient styling.

- Repository: [Dioxus Labs/dioxus](https://github.com/DioxusLabs/dioxus)
- Tags: deep-dive
- Published: 2026-07-23

---

**Dioxus implements CSS Modules through the Manganis asset system, using the `#[css_module]` attribute macro to generate type-safe class constants and the CLI to hash and transform selectors at build time.**

CSS Modules provide scoped styling that eliminates class name collisions in component-based applications. In the Dioxus ecosystem, this integration relies on a sophisticated pipeline that transforms your stylesheets at compile time and injects them at runtime. The implementation spans the Manganis macro system, the Dioxus CLI asset pipeline, and generated runtime code that ensures stylesheets load exactly once.

## The Three-Layer Architecture of CSS Modules in Dioxus

The CSS Modules integration operates across three distinct layers: the attribute macro that parses your Rust code, the CLI that processes the actual CSS files, and the runtime linking mechanism that connects everything in the browser.

### The `#[css_module]` Attribute Macro

Located in [`packages/manganis/manganis-macro/src/css_module.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/manganis/manganis-macro/src/css_module.rs), the `#[css_module]` attribute macro serves as the entry point for developers. When you apply this macro to a struct definition, it parses the CSS file path and optional `AssetOptions` from the attribute arguments.

The macro generates a **unit-struct** expansion containing a hidden module. This module includes:

- A `OnceLock` guard that manages the `<link>` element injection
- Constants representing each transformed class name from your CSS file
- The `__CssIdent` wrapper type that implements `Deref<Target=str>` and `IntoAttributeValue`

### Build-Time Asset Processing

The heavy lifting occurs in [`packages/cli/src/opt/css.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/cli/src/opt/css.rs) during the build process. The CLI invokes `process_css_module` and `transform_css` to handle the physical CSS transformation.

This pipeline executes several critical operations:

1. **Content hashing**: Generates a unique hash for the module using `create_module_hash`
2. **Selector transformation**: Rewrites every class selector to a unique hash-based name via `get_class_mappings`
3. **Minification**: Optionally minifies the output when configured through `AssetOptions`
4. **File generation**: Writes the transformed CSS to the build output directory with the hashed filename

The result is a stylesheet where `.container` becomes something like `.container-a3f7b2`, ensuring complete isolation between components.

### Runtime Stylesheet Injection and Class Resolution

At runtime, the macro-generated code in the hidden module handles lazy loading. The implementation uses a `std::sync::OnceLock` to ensure the stylesheet `<link>` element is injected exactly once per module, using `document().create_link(...)` to append it to the DOM.

Each class constant is a `__CssIdent` wrapper that implements `Deref`. When you use `Styles::container` in your RSX, the deref implementation returns the hashed class name string and triggers the stylesheet injection on first access. This approach guarantees that styles are available when needed without manual link management.

## How the `#[css_module]` Macro Expands

Understanding the macro expansion clarifies how the type-safe API works. When you write:

```rust
#[css_module("/styles/app.css")]
struct Styles;

```

The macro expands this into a unit struct with an accompanying hidden module. The hidden module contains:

- An internal `LINK` static using `OnceLock` to manage the DOM element
- Constants for each CSS class (e.g., `pub const container: __CssIdent = ...`)
- The `__CssIdent` struct definition implementing `Deref<Target = str>` and `IntoAttributeValue`

The `__CssIdent` type is crucial. It wraps the hashed class name string and implements `Deref` to return that string. When used as a class attribute in RSX, it automatically converts into the appropriate attribute value while ensuring the stylesheet is loaded via the `OnceLock` initialization logic found in lines 18-35 of [`css_module.rs`](https://github.com/DioxusLabs/dioxus/blob/main/css_module.rs).

## Build-Time CSS Transformation Pipeline

The Dioxus CLI handles CSS Modules differently than standard CSS files. In [`packages/cli/src/opt/css.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/cli/src/opt/css.rs), the `process_css_module` function orchestrates the transformation.

The pipeline follows this sequence:

1. **Parse**: Reads the source CSS file content
2. **Hash**: Generates a content hash using `create_module_hash` to create a unique module identifier
3. **Map**: Creates class-to-hash mappings using `get_class_mappings`, transforming selectors like `.button` into `.button-4d2e1a`
4. **Transform**: Rewrites the entire CSS AST with the new hashed selectors
5. **Optimize**: Applies minification if `AssetOptions` specifies `with_minify(true)`
6. **Emit**: Writes the final CSS to the output directory with the hash in the filename

This transformation ensures that the constants generated by the macro match the actual class names in the deployed stylesheet.

## Practical Usage with AssetOptions

The CSS Modules API supports configuration through the `AssetOptions` builder pattern. Here is a complete example demonstrating both basic usage and advanced configuration:

```rust
use dioxus::prelude::*;

fn app() -> Element {
    // Basic usage - simple file path
    #[css_module("/examples/assets/css_module1.css")]
    struct Styles;
    
    // Advanced configuration with AssetOptions
    #[css_module(
        "/examples/assets/css_module2.css",
        AssetOptions::css_module()
            .with_minify(true)    // Enable minification
            .with_preload(false)  // Disable preloading
    )]
    struct OtherStyles;
    
    rsx! {
        div { 
            class: Styles::container,
            div { class: OtherStyles::test, "Hello, world!" }
            div { class: OtherStyles::highlight, "Highlighted content" }
            // Global selectors bypass hashing
            div { class: Styles::global_class, "Global styling" }
        }
    }
}

```

**Global selectors** defined with the `:global()` wrapper (e.g., `:global(.global_class)`) are emitted unchanged in the transformed CSS. This allows you to reference global utility classes or third-party framework styles without the hashing transformation.

## Summary

- **Dioxus CSS Modules** rely on the Manganis asset system, combining compile-time transformation with runtime injection.
- The **`#[css_module]` macro** in [`manganis-macro/src/css_module.rs`](https://github.com/DioxusLabs/dioxus/blob/main/manganis-macro/src/css_module.rs) generates type-safe constants and manages stylesheet linking via `OnceLock`.
- The **CLI processing pipeline** in [`packages/cli/src/opt/css.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/cli/src/opt/css.rs) handles hashing (`create_module_hash`), class mapping (`get_class_mappings`), and optional minification.
- The **`__CssIdent` wrapper** implements `Deref<Target=str>` to provide seamless string conversion while triggering lazy stylesheet injection.
- **Global selectors** using `:global()` syntax bypass the hashing mechanism for framework-wide styles.

## Frequently Asked Questions

### How do I configure minification for CSS Modules in Dioxus?

Pass `AssetOptions::css_module().with_minify(true)` as the second argument to the `#[css_module]` attribute. This option is processed by the CLI in [`packages/cli/src/opt/css.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/cli/src/opt/css.rs) during the `transform_css` phase, reducing the final stylesheet size before deployment.

### What is the `__CssIdent` wrapper and why is it used?

`__CssIdent` is a generated struct that wraps hashed class name strings. It implements `Deref<Target=str>` to allow transparent use as string slices and `IntoAttributeValue` for RSX compatibility. According to the macro implementation in [`css_module.rs`](https://github.com/DioxusLabs/dioxus/blob/main/css_module.rs), this wrapper ensures the stylesheet `<link>` is injected via `OnceLock` the first time any class from that module is dereferenced.

### Can I use global CSS selectors with Dioxus CSS Modules?

Yes. Wrap selectors in `:global(...)` syntax within your CSS files. The parser in [`manganis-core/src/css_module_parser.rs`](https://github.com/DioxusLabs/dioxus/blob/main/manganis-core/src/css_module_parser.rs) recognizes these patterns and emits them without the hash suffix, allowing you to reference global utility classes or external framework styles while keeping component-specific classes scoped.

### How does Dioxus prevent class name collisions across components?

The CLI generates unique hashes for each CSS module using `create_module_hash` based on file content. Every class selector is rewritten with this hash via `get_class_mappings`, ensuring that `.button` in [`nav.css`](https://github.com/DioxusLabs/dioxus/blob/main/nav.css) and `.button` in [`card.css`](https://github.com/DioxusLabs/dioxus/blob/main/card.css) become distinct class names (e.g., `.button-a1b2c3` and `.button-x9y8z7`) in the final build output.