# How to Use Signals for Client-Side State Management in Topcoat

> Master client-side state management in Topcoat with signals. Learn to declare, read, and mutate signals for automatic UI updates in your web applications.

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

---

**Declare signals in your `view!` blocks using the `signal` keyword, read them by referencing their identifier in templates, and mutate them using methods like `set()` or `update()` inside client-side event handlers to trigger automatic UI updates.**

In the `tokio-rs/topcoat` framework, signals provide a lightweight, reactive mechanism for managing UI state exclusively in the browser. Understanding how to use signals for client-side state management in Topcoat allows you to build interactive interfaces without writing JavaScript, leveraging Rust's type safety throughout your application.

## Declaring Signals in View Macros

Signals are declared inside `view!` or `page!` blocks using the `signal` keyword followed by an initial value. This creates a `Signal<T>` where `T` is inferred from the provided value.

In [`examples/runtime/src/counter.rs`](https://github.com/tokio-rs/topcoat/blob/main/examples/runtime/src/counter.rs), a numeric signal is declared as follows:

```rust
view! {
    // Creates a Signal<f64> with initial value 0.0
    signal count = 0.0;
    
    <p>"Current count: {count}"</p>
}

```

The signal becomes available throughout the enclosing view block and can hold any Rust type that implements the necessary serialization traits for cross-boundary communication.

## Reading and Interpolating Signal Values

Reading a signal's current value requires only referencing its identifier. Topcoat automatically subscribes the surrounding template to changes, ensuring the DOM updates when the value mutates.

You can interpolate signals into text content, attributes, or conditional expressions:

```rust
view! {
    signal show = false;
    
    // Boolean signal controls visibility
    <div hidden={!show}>
        "Content appears when show is true"
    </div>
    
    <button on:click=|cx| { show.toggle(); }>
        "Toggle Visibility"
    </button>
}

```

## Mutating Signals from Client-Side Code

Signals enforce a strict client-side mutation policy. You can only write to signals from within client-side event handlers or the `expr!` macro, never from server-side rendering contexts.

Available mutation methods include:

- **`signal.set(new_value)`** — Replaces the current value entirely
- **`signal.update(|v| ...)`** — Mutates the value using a closure
- **`signal.toggle()`** — Available for boolean signals, flips the value

The following example from [`examples/runtime/src/counter.rs`](https://github.com/tokio-rs/topcoat/blob/main/examples/runtime/src/counter.rs) demonstrates incrementing a counter:

```rust
view! {
    signal count = 0.0;
    
    <p>"Current count: {count}"</p>
    
    <button on:click=|cx| {
        // Runs only in the browser
        count.update(|v| *v + 1.0);
    }>
        "Increment"
    </button>
}

```

For text inputs, bind the signal to the `value` attribute and update it on input events:

```rust
view! {
    signal input = String::new();
    
    <input type="text"
           value={input}
           on:input=|cx| {
               input.set(event.target().value());
           } />
    <p>"You typed: {input}"</p>
}

```

## Automatic Reactivity and DOM Updates

When a signal's value changes, Topcoat automatically re-evaluates any dependent UI components without requiring a full page reload. This reactivity is implemented in [`crates/topcoat-runtime/src/signal.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/src/signal.rs), where the `Signal<T>` type tracks subscriptions and notifies listeners upon mutation.

The framework compiles signal mutations to JavaScript that executes in the browser, maintaining a lightweight bridge between Rust's type system and the DOM.

## Server-Side Restrictions and Safety

Topcoat strictly enforces that signals remain client-side constructs. Attempting to invoke write operations on a signal during server-side rendering triggers a panic.

As implemented in [`crates/topcoat-runtime/src/surrogate/signal.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/src/surrogate/signal.rs), the runtime panics with the message:

> "expressions in which a signal is written to cannot be run server-side"

This design guarantees that signals never leak server-side state to the client inappropriately, and vice versa, maintaining clear boundaries between server-rendered HTML and client-side interactivity.

## Using Signals with Shards

For components that require server-side re-rendering when client-side signals change, Topcoat provides the `shard!` macro. This pattern, demonstrated in [`examples/shard/src/main.rs`](https://github.com/tokio-rs/topcoat/blob/main/examples/shard/src/main.rs), creates a server component that re-renders when bound signals update:

```rust
view! {
    signal input = String::new();
    
    <shard!>
        <p>"Live preview: {input}"</p>
    </shard!>
    
    <input type="text"
           value={input}
           on:input=|cx| input.set(event.target().value()) />
}

```

The `shard!` component receives the signal value from the client and re-renders on the server, bridging the gap between client-side state and server-side templates.

## Summary

- **Declare signals** using `signal name = value;` syntax inside `view!` blocks to create reactive `Signal<T>` values
- **Read signals** by referencing their identifier in templates; changes trigger automatic DOM updates via the implementation in [`crates/topcoat-runtime/src/signal.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/src/signal.rs)
- **Mutate signals** only from client-side handlers using `set()`, `update()`, or `toggle()` methods
- **Respect server boundaries** — writing signals server-side panics as enforced by [`crates/topcoat-runtime/src/surrogate/signal.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/src/surrogate/signal.rs)
- **Combine with shards** to enable server-side re-rendering driven by client-side signal changes

## Frequently Asked Questions

### What happens if I try to write a signal on the server?

The application will panic with the message "expressions in which a signal is written to cannot be run server-side". This panic originates in [`crates/topcoat-runtime/src/surrogate/signal.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/src/surrogate/signal.rs) and ensures signals remain strictly client-side constructs, preventing accidental server-side mutations of browser-only state.

### Can I use custom Rust types with Topcoat signals?

Yes. Signals are generic over `Signal<T>`, allowing any Rust type that implements the necessary serialization traits. You can declare `signal user = User::default();` or any custom struct, provided the type can be serialized for the client-server boundary.

### How do signals differ from server-side state management in Topcoat?

Signals exist only in the browser and are not persisted on the server, whereas server-side state lives in your Rust application logic and renders HTML. Signals are ideal for ephemeral UI state like toggles, form inputs, and counters that do not require server persistence or database storage.

### Do signals require manual JavaScript handling?

No. Topcoat compiles signal mutations to JavaScript automatically through the `expr!` macro and event handler syntax. You write Rust code using methods like `signal.set()` or `signal.update()`, and the framework generates the necessary browser-side JavaScript to execute these operations.