Reactive Signals

In a hypermedia approach, the backend drives state to the frontend and acts as the primary source of truth. It’s up to the backend to determine what actions the user can take next by patching appropriate elements in the DOM.

Sometimes, however, you may need access to frontend state that’s driven by user interactions. Click, input and keydown events are some of the more common user events that you’ll want your frontend to be able to react to.

Datastar uses signals to manage frontend state. You can think of signals as reactive variables that automatically track and propagate changes in and to Datastar expressions. Signals are denoted using the $ prefix.

Data Attributes #

Datastar allows you to add reactivity to your frontend and interact with your backend in a declarative way using data-* attributes.

The Datastar VSCode extension and IntelliJ plugin provide autocompletion for all data-* attributes.

data-bind #

The data-bind attribute sets up two-way data binding on any HTML element that receives user input or selections. These include input, textarea, select, checkbox and radio elements, as well as web components whose value can be made reactive.

1<input data-bind-foo />

This creates a new signal that can be called using $foo, and binds it to the element’s value. If either is changed, the other automatically updates.

You can accomplish the same thing passing the signal name as a value, an alternate syntax that might be more useful for some templating languages:

1<input data-bind="foo" />

data-text #

The data-text attribute sets the text content of an element to the value of a signal. The $ prefix is required to denote a signal.

1<input data-bind-foo />
2<div data-text="$foo"></div>
Demo

The value of the data-text attribute is a Datastar expression that is evaluated, meaning that we can use JavaScript in it.

1<input data-bind-foo />
2<div data-text="$foo.toUpperCase()"></div>
Demo

data-computed #

The data-computed attribute creates a new signal that is derived from a reactive expression. The computed signal is read-only, and its value is automatically updated when any signals in the expression are updated.

1<input data-bind-foo />
2<div data-computed-repeated="$foo.repeat(2)" data-text="$repeated"></div>

This results in the $repeated signal’s value always being equal to the value of the $foo signal repeated twice. Computed signals are useful for memoizing expressions containing other signals.

Demo

data-show #

The data-show attribute can be used to show or hide an element based on whether an expression evaluates to true or false.

1<input data-bind-foo />
2<button data-show="$foo != ''">Save</button>

This results in the button being visible only when the input value is not an empty string. This could also be shortened to data-show="!$foo".

Demo

data-class #

The data-class attribute allows us to add or remove an element’s class based on an expression.

1<input data-bind-foo />
2<button data-class-success="$foo != ''">
3    Save
4</button>

If the expression evaluates to true, the success class is added to the element; otherwise, it is removed.

Demo

The data-class attribute can also be used to add or remove multiple classes from an element using a set of key-value pairs, where the keys represent class names and the values represent expressions.

1<button data-class="{success: $foo != '', 'font-bold': $foo == 'bar'}">
2    Save
3</button>

data-attr #

The data-attr attribute can be used to bind the value of any HTML attribute to an expression.

1<input data-bind-foo />
2<button data-attr-disabled="$foo == ''">
3    Save
4</button>

This results in a disabled attribute being given the value true whenever the input is an empty string.

Demo

The data-attr attribute can also be used to set the values of multiple attributes on an element using a set of key-value pairs, where the keys represent attribute names and the values represent expressions.

1<button data-attr="{disabled: $foo == '', title: $foo}">Save</button>

data-signals #

Signals are globally accessible from anywhere in the DOM. So far, we’ve created signals on the fly using data-bind and data-computed. If a signal is used without having been created, it will be created automatically and its value set to an empty string.

Another way to create signals is using the data-signals attribute, which patches (adds, updates or removes) one or more signals into the existing signals.

1<div data-signals-foo="1"></div>

Signals can be nested using dot-notation.

1<div data-signals-form.foo="2"></div>

The data-signals attribute can also be used to patch multiple signals using a set of key-value pairs, where the keys represent signal names and the values represent expressions.

1<div data-signals="{foo: 1, form: {foo: 2}}"></div>

data-on #

The data-on attribute can be used to attach an event listener to an element and run an expression whenever the event is triggered.

1<input data-bind-foo />
2<button data-on-click="$foo = ''">
3    Reset
4</button>

This results in the $foo signal’s value being set to an empty string whenever the button element is clicked. This can be used with any valid event name such as data-on-keydown, data-on-mouseover, etc. Custom events may also be used.

Demo

These are just some of the attributes available in Datastar. For a complete list, see the attribute reference.

Frontend Reactivity #

Datastar’s data attributes enable declarative signals and expressions, providing a simple yet powerful way to add reactivity to the frontend.

Datastar expressions are strings that are evaluated by Datastar attributes and actions. While they are similar to JavaScript, there are some important differences that are explained in the next section of the guide.

1<div data-signals-hal="'...'">
2    <button data-on-click="$hal = 'Affirmative, Dave. I read you.'">
3        HAL, do you read me?
4    </button>
5    <div data-text="$hal"></div>
6</div>
Demo

See if you can figure out what the code below does based on what you’ve learned so far, before trying the demo below it.

 1<div
 2    data-signals="{response: '', answer: 'bread'}"
 3    data-computed-correct="$response.toLowerCase() == $answer"
 4>
 5    <div id="question">What do you put in a toaster?</div>
 6    <button data-on-click="$response = prompt('Answer:') ?? ''">BUZZ</button>
 7    <div data-show="$response != ''">
 8        You answered “<span data-text="$response"></span>”.
 9        <span data-show="$correct">That is correct ✅</span>
10        <span data-show="!$correct">
11        The correct answer is “
12        <span data-text="$answer"></span>
13        ” 🤷
14        </span>
15    </div>
16</div>
Demo

What do you put in a toaster?

You answered “”. That is correct ✅ The correct answer is “bread” 🤷

The Datastar Inspector can be used to inspect and filter current signals and view signal patch events in real-time.

Patching Signals #

Remember that in a hypermedia approach, the backend drives state to the frontend. Just like with elements, frontend signals can be patched (added, updated and removed) from the backend using backend actions.

1<div data-signals-hal="'...'">
2    <button data-on-click="@get('/endpoint')">
3        HAL, do you read me?
4    </button>
5    <div data-text="$hal"></div>
6</div>

If a response has a content-type of application/json, the signal values are patched into the frontend signals.

We call this a “Patch Signals” event because multiple signals can be patched (using JSON Merge Patch RFC 7396) into the existing signals.

1{"hal": "Affirmative, Dave. I read you."}
Demo

If the response has a content-type of text/event-stream, it can contain zero or more SSE events. The example above can be replicated using a datastar-patch-signals SSE event.

1event: datastar-patch-signals
2data: signals {hal: 'Affirmative, Dave. I read you.'}

Here’s the code to generate the SSE event above using the Go SDK.

1sse := datastar.NewSSE(writer, request)
2sse.PatchSignals([]byte(`{hal: 'Affirmative, Dave. I read you.'}`))

Because we can send as many events as we want in a stream, and because it can be a long-lived connection, we can extend the example above to first set the hal signal to an “affirmative” response and then, after a few seconds, reset the signal.

1event: datastar-patch-signals
2data: signals {hal: 'Affirmative, Dave. I read you.'}
3
4// Wait 1 second
5
6event: datastar-patch-signals
7data: signals {hal: '...'}
Demo

Here’s the code to generate the SSE events above using the Go SDK.

1sse := datastar.NewSSE(writer, request)
2sse.PatchSignals([]byte(`{hal: 'Affirmative, Dave. I read you.'}`))
3time.Sleep(1 * time.Second)
4sse.PatchSignals([]byte(`{hal: '...'}`))
In addition to your browser’s dev tools, the Datastar Inspector can be used to monitor and inspect SSE events received by Datastar.

We’ll cover event streams and SSE events in more detail later in the guide, but as you can see they are just plain text events with a special syntax, made simpler by the SDKs.