# How to Create Custom Components with the `#[component]` Macro in Topcoat

> Learn to create custom components with the #[component] macro in Topcoat. Transform async functions into reusable UI components for declarative use in view! blocks. Effortlessly build your UI.

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

---

**Topcoat transforms async functions into reusable UI components using the `#[component]` attribute, which automatically implements the `Component` trait and wires props builders so your functions can be used declaratively inside `view!` blocks.**

Topcoat is an async Rust web framework that turns function definitions into declarative UI components. When you create custom components with the `#[component]` macro, the system generates the boilerplate needed to integrate with the view system defined in [`crates/topcoat-view/src/component.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/src/component.rs), letting you focus on rendering logic while the macro handles trait implementations and type conversions.

## How the `#[component]` Macro Works

Under the hood, the `#[component]` macro—implemented in [`crates/topcoat-view/macro/src/lib.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/macro/src/lib.rs)—expands your function into a type that implements the **`Component`** trait. This trait requires a `Props` associated type and a `render` method that returns a `View`. The macro automatically generates a props builder, handles the `Future` wrapping, and registers the component with the view system so it can be instantiated like a native HTML element.

The transformation follows three specific stages:
- **Props struct generation** – The macro looks for a `Props` type that implements the `Props` trait (usually derived with `#[derive(Props)]`).
- **Render implementation** – The function body is wrapped in a `Future` that receives a request-scoped **`Cx`** context.
- **View system integration** – The component becomes available inside other `view!` blocks with automatic context propagation.

## Step 1: Define Props with `#[derive(Props)]`

Every component starts with a props struct that defines its inputs. Use `#[derive(Props)]` to generate a builder pattern that the macro will use automatically. Props can include static data, optional callbacks, and special fields like `Children`.

```rust
use topcoat_view::{Props, PropsBuilder};

#[derive(Props)]
pub struct ButtonProps {
    pub label: String,
    
    #[prop(optional)]
    pub on_click: Option<topcoat_runtime::Handler>,
}

```

The `#[prop(optional)]` attribute marks fields that may be omitted during component instantiation. The derive macro creates a `ButtonProps::builder()` method that the `#[component]` macro invokes internally when parsing `view!` syntax.

## Step 2: Write the Component Function

Mark an async function with `#[component]` and accept `&Cx` as the first argument followed by your props struct. The function must return a **`View`**, typically constructed using the **`view!`** macro.

```rust
use topcoat_core::context::Cx;
use topcoat_view::{view, View};

#[component]
pub async fn Button(cx: &Cx, props: ButtonProps) -> View {
    view! {
        button
            class = "px-4 py-2 rounded bg-blue-600 text-white"
            on:click = move |_| {
                if let Some(handler) = props.on_click.clone() {
                    handler.call(cx.clone());
                }
            }
        { props.label }
    }
}

```

The **`Cx`** parameter provides access to the request-scoped context, allowing handlers to interact with the runtime. The macro expands this function into a struct that implements `Component`, wiring the props builder and creating the async `render` method required by the trait defined in [`crates/topcoat-view/src/component.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/src/component.rs).

## Step 3: Use Components in `view!` Blocks

Once defined, components function as first-class citizens within the view system. Invoke them inside `view!` blocks using XML-like syntax, passing props as attributes.

```rust
use topcoat_view::{view, View};

#[component]
pub async fn Page(cx: &Cx) -> View {
    view! {
        div class = "space-y-4" {
            Button {
                label: "Click me".into(),
                on_click: Some(topcoat_runtime::handler!(|cx| async move {
                    println!("Button pressed!");
                }))
            }
            
            Button {
                label: "No action".into(),
                on_click: None,
            }
        }
    }
}

```

The `view!` macro automatically passes the `Cx` context, builds the props using the generated builder, and awaits the component's render future.

## Advanced: Accepting Children with the `Children` Type

Components that wrap content require a special **`Children`** prop. Include `Option<Children>` in your props struct to accept arbitrary child markup, then render it explicitly within your component logic.

```rust
use topcoat_view::{Props, Children};

#[derive(Props)]
pub struct CardProps {
    pub title: String,
    #[prop(optional)]
    pub children: Option<Children>,
}

#[component]
pub async fn Card(cx: &Cx, props: CardProps) -> View {
    view! {
        div class = "border rounded p-4 shadow" {
            h2 { { props.title } }
            if let Some(ch) = props.children.clone() {
                ch.render(cx).await?;
            }
        }
    }
}

```

Usage follows block syntax where nested elements become the children:

```rust
view! {
    Card { title: "Dashboard".into() } {
        p { "Welcome to the dashboard!" }
        Button { label: "Refresh".into(), on_click: None }
    }
}

```

The `Children` type implements a `render` method that takes the `Cx` context and returns a `View`, allowing you to control exactly where child content appears in the output.

## What the Macro Generates

The `#[component]` macro expands your annotated function into approximately the following code (simplified for clarity):

```rust
pub struct Button;

impl Component for Button {
    type Props = ButtonProps;
    
    fn props_builder() -> <Self::Props as Props>::Builder {
        ButtonProps::builder()
    }
    
    fn render<'cx>(
        self, 
        cx: &'cx Cx, 
        props: Self::Props
    ) -> impl Future<Output = Result<View, Error>> + Send {
        async move {
            // Original function body here
        }
    }
}

```

This matches the `Component` trait signature found in [`crates/topcoat-view/src/component.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/src/component.rs) (lines 5–22), demonstrating how the macro bridges your high-level function definition with the framework's underlying trait requirements.

## Summary

- **Define typed inputs** using a struct with `#[derive(Props)]`, including optional fields marked with `#[prop(optional)]`.
- **Implement the component** as an async function with `#[component]`, accepting `&Cx` and props, returning a `View` built with the `view!` macro.
- **Consume components** inside other `view!` blocks using tag-like syntax; the macro handles context propagation, props building, and trait implementation automatically.

## Frequently Asked Questions

### What is the `Cx` parameter in component functions?

**`Cx`** (Context) is a request-scoped handle provided by the Topcoat runtime that gives components access to handlers, state, and other request-specific resources. It must be the first parameter in any `#[component]` function and is automatically cloned when passed to child components or async handlers.

### Can I use components outside of `view!` blocks?

While components are designed for declarative use within `view!` macros, you can instantiate them programmatically by calling the generated `Component::render` method directly with a `Cx` reference and manually constructed props. However, this bypasses the ergonomic props builder and is generally only needed for testing or dynamic composition.

### How do optional props affect the component builder?

When a field is marked with `#[prop(optional)]`, the generated builder allows the field to be omitted. If omitted, the field receives its `Default` value (typically `None` for `Option<T>` types). This enables flexible APIs where callbacks or configuration values can be provided conditionally without requiring placeholder values at every call site.

### Where can I find real-world examples of custom components?

The repository includes production-ready examples in [`examples/ui/src/components/button.rs`](https://github.com/tokio-rs/topcoat/blob/main/examples/ui/src/components/button.rs), demonstrating patterns for event handling, conditional styling, and children propagation. The macro's full documentation resides in [`crates/topcoat-view/macro/docs/component.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/macro/docs/component.md), covering additional attributes and edge cases.