Roc Signals

Roc Signals is a Roc platform for building interactive browser interfaces. You describe elements, state, and the computations that connect them. The runtime stores the current values and updates the affected parts of the page when an event changes state.

You can use ordinary HTML and CSS, including your existing stylesheets. The examples on this site use Tailwind classes, but the platform does not require Tailwind or a component library.

Start here

If you want to run an app first, follow Getting Started. It covers the compiler, platform, native tests, and browser setup.

If signals are new to you, Thinking in Signals explains how state and derived values fit together. It introduces the terms used throughout these guides; experience with another UI framework is not required.

The Tutorial builds a reading list with inputs, filtering, keyed rows, and tests. Use it when you want to see those pieces in one app. The code assumes some familiarity with Roc functions, records, and tag unions; the Roc tutorial covers the language itself.

A small example

Inside a checkout of this repository, save this as examples-web/hello/main.roc:

app [main] { pf: platform "../../platform-web/main.roc" }

import pf.Elem exposing [Elem]
import pf.Html
import pf.Ui

main : () -> Elem
main = ||
    Ui.state(
        0.I64,
        |count| {
            label = count.signal().map(|n| "Count: ${n.to_str()}")

            Html.section(
                "Counter",
                [],
                [
                    Html.paragraph_s_attrs(label, [Html.test_id("count")]),
                    Html.button("Increment", count.on_unit(|n| n + 1)),
                ],
            )
        },
    )

Ui.state declares a state value starting at zero. count.signal() refers to its current value, and map describes how to turn that value into display text. The button’s reducer, |n| n + 1, computes the next count.

On a click, the runtime calls the reducer, evaluates the dependent text transform, and updates the paragraph. It does not call main again. If a recomputed value compares equal to its previous value, propagation stops at that edge. New conditional branches and list rows can still call builders to create their UI when they become live.

Getting Started has the commands to check, test, and run this kind of app. A standalone app can use a released platform archive instead of the checkout-relative path shown here.

Find a topic

I want to…Read
Handle input, validate a form, or coordinate state changesState, Events, and Forms
Render lists, choose branches, or reuse a componentLists, Conditionals, and Components
Make HTTP requests, use timers, navigate, or store a draftEffects, HTTP, and the Browser
Split an app into modules and decide where state belongsStructuring an App
Test interactions, task results, cleanup, and update workTesting
Understand runtime ownership and update costsUnder the Hood
Look up a function or supported browser featureReference
Change or test the platform itselfContributing

The examples include browser apps, Roc source, and native specs. Pick one with a similar problem to yours: a form, a routed app, an editor, or a table.

Before choosing it for a project

Roc Signals and Roc are still evolving. Use the compiler version named by your platform release, keep the browser runtime and platform compatible, and read the release notes when upgrading.

The platform runs on the client. It does not provide server rendering or hydration. Routing is application code over Browser.location(). HTML and SVG are supported, while some browser capabilities require a JavaScript behaviour or are unavailable through the current API. Check the browser limits before depending on one.

Native specs exercise the same reactive engine as the browser build. They are useful for state, ordering, and lifecycle tests, but they do not establish CSS layout, real keyboard behaviour, or accessibility in a browser. Test those interactions in a browser as well.