Guide
Getting Started
Datastar simplifies frontend development, allowing you to build backend-driven, interactive UIs using a hypermedia-first approach that extends and enhances HTML.
Datastar provides backend reactivity like htmx and frontend reactivity like Alpine.js in a lightweight frontend framework that doesn’t require any npm packages or other dependencies. It provides two primary functions:
- Modify the DOM and state by sending events from your backend.
- Build reactivity into your frontend using standard
data-*
HTML attributes.
Other useful resources include an AI-generated deep wiki, LLM-ingestible code samples, and single-page docs.
Installation #
The quickest way to use Datastar is to include it using a script
tag that fetches it from a CDN.
1<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"></script>
If you prefer to host the file yourself, download the script or create your own bundle using the bundler, then include it from the appropriate path.
1<script type="module" src="/path/to/datastar.js"></script>
data-*
#
At the core of Datastar are data-*
HTML attributes (hence the name). They allow you to add reactivity to your frontend and interact with your backend in a declarative way.
The Datastar VSCode extension and IntelliJ plugin provide autocompletion for all available data-*
attributes.
The data-on
attribute can be used to attach an event listener to an element and execute an expression whenever the event is triggered. The value of the attribute is a Datastar expression in which JavaScript can be used.
We’ll explore more data attributes in the next section of the guide.
Patching Elements #
With Datastar, the backend drives the frontend by patching (adding, updating and removing) HTML elements in the DOM.
Datastar receives elements from the backend and manipulates the DOM using a morphing strategy (by default). Morphing ensures that only modified parts of the DOM are updated, preserving state and improving performance.
Datastar provides actions for sending requests to the backend. The @get()
action sends a GET
request to the provided URL using a fetch request.
Actions in Datastar are helper functions that have the syntax @actionName()
. Read more about actions in the reference.
If the response has a content-type
of text/html
, the top-level HTML elements will be morphed into the existing DOM based on the element IDs.
We call this a “Patch Elements” event because multiple elements can be patched into the DOM at once.
In the example above, the DOM must contain an element with a hal
ID in order for morphing to work. Other patching strategies are available, but morph is the best and simplest choice in most scenarios.
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-elements
SSE event.
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 send HAL’s response and then, after a few seconds, reset the text.
Here’s the code to generate the SSE events above using the SDKs.
1;; Import the SDK's api and your adapter
2(require
3 '[starfederation.datastar.clojure.api :as d*]
4 '[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]])
5
6;; in a ring handler
7(defn handler [request]
8 ;; Create an SSE response
9 (->sse-response request
10 {on-open
11 (fn [sse]
12 ;; Patches elements into the DOM
13 (d*/patch-elements! sse
14 "<div id=\"hal\">I’m sorry, Dave. I’m afraid I can’t do that.</div>")
15 (Thread/sleep 1000)
16 (d*/patch-elements! sse
17 "<div id=\"hal\">Waiting for an order...</div>"))}))
1using StarFederation.Datastar.DependencyInjection;
2
3// Adds Datastar as a service
4builder.Services.AddDatastar();
5
6app.MapGet("/", async (IDatastarService datastarService) =>
7{
8 // Patches elements into the DOM.
9 await datastarService.PatchElementsAsync(@"<div id=""hal"">I’m sorry, Dave. I’m afraid I can’t do that.</div>");
10
11 await Task.Delay(TimeSpan.FromSeconds(1));
12
13 await datastarService.PatchElementsAsync(@"<div id=""hal"">Waiting for an order...</div>");
14});
1import (
2 "github.com/starfederation/datastar-go/datastar"
3 time
4)
5
6// Creates a new `ServerSentEventGenerator` instance.
7sse := datastar.NewSSE(w,r)
8
9// Patches elements into the DOM.
10sse.PatchElements(
11 `<div id="hal">I’m sorry, Dave. I’m afraid I can’t do that.</div>`
12)
13
14time.Sleep(1 * time.Second)
15
16sse.PatchElements(
17 `<div id="hal">Waiting for an order...</div>`
18)
1import starfederation.datastar.utils.ServerSentEventGenerator;
2
3// Creates a new `ServerSentEventGenerator` instance.
4AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response);
5ServerSentEventGenerator generator = new ServerSentEventGenerator(responseAdapter);
6
7// Patches elements into the DOM.
8generator.send(PatchElements.builder()
9 .data("<div id=\"hal\">I’m sorry, Dave. I’m afraid I can’t do that.</div>")
10 .build()
11);
12
13Thread.sleep(1000);
14
15generator.send(PatchElements.builder()
16 .data("<div id=\"hal\">Waiting for an order...</div>")
17 .build()
18);
1use starfederation\datastar\ServerSentEventGenerator;
2
3// Creates a new `ServerSentEventGenerator` instance.
4$sse = new ServerSentEventGenerator();
5
6// Patches elements into the DOM.
7$sse->patchElements(
8 '<div id="hal">I’m sorry, Dave. I’m afraid I can’t do that.</div>'
9);
10
11sleep(1)
12
13$sse->patchElements(
14 '<div id="hal">Waiting for an order...</div>'
15);
1from datastar_py import ServerSentEventGenerator as SSE
2from datastar_py.sanic import datastar_response
3
4@app.get('/open-the-bay-doors')
5@datastar_response
6async def open_doors(request):
7 yield SSE.patch_elements('<div id="hal">I’m sorry, Dave. I’m afraid I can’t do that.</div>')
8 await asyncio.sleep(1)
9 yield SSE.patch_elements('<div id="hal">Waiting for an order...</div>')
1require 'datastar'
2
3# Create a Datastar::Dispatcher instance
4
5datastar = Datastar.new(request:, response:)
6
7# In a Rack handler, you can instantiate from the Rack env
8# datastar = Datastar.from_rack_env(env)
9
10# Start a streaming response
11datastar.stream do |sse|
12 # Patches elements into the DOM.
13 sse.patch_elements %(<div id="hal">I’m sorry, Dave. I’m afraid I can’t do that.</div>)
14
15 sleep 1
16
17 sse.patch_elements %(<div id="hal">Waiting for an order...</div>)
18end
1use async_stream::stream;
2use datastar::prelude::*;
3use std::thread;
4use std::time::Duration;
5
6Sse(stream! {
7 // Patches elements into the DOM.
8 yield PatchElements::new("<div id='hal'>I’m sorry, Dave. I’m afraid I can’t do that.</div>").into();
9
10 thread::sleep(Duration::from_secs(1));
11
12 yield PatchElements::new("<div id='hal'>Waiting for an order...</div>").into();
13})
1// Creates a new `ServerSentEventGenerator` instance (this also sends required headers)
2ServerSentEventGenerator.stream(req, res, (stream) => {
3 // Patches elements into the DOM.
4 stream.patchElements(`<div id="hal">I’m sorry, Dave. I’m afraid I can’t do that.</div>`);
5
6 setTimeout(() => {
7 stream.patchElements(`<div id="hal">Waiting for an order...</div>`);
8 }, 1000);
9});
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.
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 available 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.
The value of the data-text
attribute is a Datastar expression that is evaluated, meaning that we can use JavaScript in it.
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.
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.
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
.
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"
.
data-class
#
The data-class
attribute allows us to add or remove an element’s class based on an expression.
If the expression evaluates to true
, the success
class is added to the element; otherwise, it is removed.
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.
data-attr
#
The data-attr
attribute can be used to bind the value of any HTML attribute to an expression.
This results in a disabled
attribute being given the value true
whenever the input is an empty string.
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.
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.
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.
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>
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.
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."}
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.
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 second, reset the signal.
Here’s the code to generate the SSE events above using the SDKs.
1;; Import the SDK's api and your adapter
2(require
3 '[starfederation.datastar.clojure.api :as d*]
4 '[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]])
5
6;; in a ring handler
7(defn handler [request]
8 ;; Create an SSE response
9 (->sse-response request
10 {on-open
11 (fn [sse]
12 ;; Patches signal.
13 (d*/patch-signals! sse "{hal: 'Affirmative, Dave. I read you.'}")
14 (Thread/sleep 1000)
15 (d*/patch-signals! sse "{hal: '...'}"))}))
1using StarFederation.Datastar.DependencyInjection;
2
3// Adds Datastar as a service
4builder.Services.AddDatastar();
5
6app.MapGet("/hal", async (IDatastarService datastarService) =>
7{
8 // Patches signals.
9 await datastarService.PatchSignalsAsync(new { hal = "Affirmative, Dave. I read you" });
10
11 await Task.Delay(TimeSpan.FromSeconds(3));
12
13 await datastarService.PatchSignalsAsync(new { hal = "..." });
14});
1import (
2 "github.com/starfederation/datastar-go/datastar"
3)
4
5// Creates a new `ServerSentEventGenerator` instance.
6sse := datastar.NewSSE(w, r)
7
8// Patches signals
9sse.PatchSignals([]byte(`{hal: 'Affirmative, Dave. I read you.'}`))
10
11time.Sleep(1 * time.Second)
12
13sse.PatchSignals([]byte(`{hal: '...'}`))
1import starfederation.datastar.utils.ServerSentEventGenerator;
2
3// Creates a new `ServerSentEventGenerator` instance.
4AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response);
5ServerSentEventGenerator generator = new ServerSentEventGenerator(responseAdapter);
6
7// Patches signals.
8generator.send(PatchSignals.builder()
9 .data("{\"hal\": \"Affirmative, Dave. I read you.\"}")
10 .build()
11);
12
13Thread.sleep(1000);
14
15generator.send(PatchSignals.builder()
16 .data("{\"hal\": \"...\"}")
17 .build()
18);
1from datastar_py import ServerSentEventGenerator as SSE
2from datastar_py.sanic import datastar_response
3
4@app.get('/do-you-read-me')
5@datastar_response
6async def open_doors(request):
7 yield SSE.patch_signals({"hal": "Affirmative, Dave. I read you."})
8 await asyncio.sleep(1)
9 yield SSE.patch_signals({"hal": "..."})
1require 'datastar'
2
3# Create a Datastar::Dispatcher instance
4
5datastar = Datastar.new(request:, response:)
6
7# In a Rack handler, you can instantiate from the Rack env
8# datastar = Datastar.from_rack_env(env)
9
10# Start a streaming response
11datastar.stream do |sse|
12 # Patches signals
13 sse.patch_signals(hal: 'Affirmative, Dave. I read you.')
14
15 sleep 1
16
17 sse.patch_signals(hal: '...')
18end
1use async_stream::stream;
2use datastar::prelude::*;
3use std::thread;
4use std::time::Duration;
5
6Sse(stream! {
7 // Patches signals.
8 yield PatchSignals::new("{hal: 'Affirmative, Dave. I read you.'}").into();
9
10 thread::sleep(Duration::from_secs(1));
11
12 yield PatchSignals::new("{hal: '...'}").into();
13})
1// Creates a new `ServerSentEventGenerator` instance (this also sends required headers)
2ServerSentEventGenerator.stream(req, res, (stream) => {
3 // Patches signals.
4 stream.patchSignals({'hal': 'Affirmative, Dave. I read you.'});
5
6 setTimeout(() => {
7 stream.patchSignals({'hal': '...'});
8 }, 1000);
9});
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.
Datastar Expressions
Datastar expressions are strings that are evaluated by data-*
attributes. While they are similar to JavaScript, there are some important differences that make them more powerful for declarative hypermedia applications.
Datastar Expressions #
The following example outputs 1
because we’ve defined foo
as a signal with the initial value 1
, and are using $foo
in a data-*
attribute.
A variable el
is available in every Datastar expression, representing the element that the attribute is attached to.
1<div id="foo" data-text="el.id"></div>
When Datastar evaluates the expression $foo
, it first converts it to the signal value, and then evaluates that expression in a sandboxed context. This means that JavaScript can be used in Datastar expressions.
1<div data-text="$foo.length"></div>
JavaScript operators are also available in Datastar expressions. This includes (but is not limited to) the ternary operator ?:
, the logical OR operator ||
, and the logical AND operator &&
. These operators are helpful in keeping Datastar expressions terse.
1// Output one of two values, depending on the truthiness of a signal
2<div data-text="$landingGearRetracted ? 'Ready' : 'Waiting'"></div>
3
4// Show a countdown if the signal is truthy or the time remaining is less than 10 seconds
5<div data-show="$landingGearRetracted || $timeRemaining < 10">
6 Countdown
7</div>
8
9// Only send a request if the signal is truthy
10<button data-on-click="$landingGearRetracted && @post('/launch')">
11 Launch
12</button>
Multiple statements can be used in a single expression by separating them with a semicolon.
Expressions may span multiple lines, but a semicolon must be used to separate statements. Unlike JavaScript, line breaks alone are not sufficient to separate statements.
Using JavaScript #
Most of your JavaScript logic should go in data-*
attributes, since reactive signals and actions only work in Datastar expressions.
Caution: if you find yourself trying to do too much in Datastar expressions, you are probably overcomplicating it™.
Any additional JavaScript functionality you require that cannot belong in data-*
attributes should be extracted out into external scripts or, better yet, web components.
Always encapsulate state and send props down, events up.
External Scripts #
When using external scripts, pass data into functions via arguments and return a result or listen for custom events dispatched from them props down, events up.
In this way, the function is encapsulated – all it knows is that it receives input via an argument, acts on it, and optionally returns a result or dispatches a custom event – and data-*
attributes can be used to drive reactivity.
If your function call is asynchronous then it will need to dispatch a custom event containing the result. While asynchronous code can be placed within Datastar expressions, Datastar will not await it.
See the sortable example.
Web Components #
Web components allow you create reusable, encapsulated, custom elements. They are native to the web and require no external libraries or frameworks. Web components unlock custom elements – HTML tags with custom behavior and styling.
When using web components, pass data into them via attributes and listen for custom events dispatched from them (props down, events up).
In this way, the web component is encapsulated – all it knows is that it receives input via an attribute, acts on it, and optionally dispatches a custom event containing the result – and data-*
attributes can be used to drive reactivity.
1class MyComponent extends HTMLElement {
2 static get observedAttributes() {
3 return ['src'];
4 }
5
6 attributeChangedCallback(name, oldValue, newValue) {
7 const value = `You entered: ${newValue}`;
8 this.dispatchEvent(
9 new CustomEvent('mycustomevent', {detail: {value}})
10 );
11 }
12}
13
14customElements.define('my-component', MyComponent);
Since the value
attribute is allowed on web components, it is also possible to use data-bind
to bind a signal to the web component’s value. Note that a change
event must be dispatched so that the event listener used by data-bind
is triggered by the value change.
See the web component example.
Executing Scripts #
Just like elements and signals, the backend can also send JavaScript to be executed on the frontend using backend actions.
If a response has a content-type
of text/javascript
, the value will be executed as JavaScript in the browser.
1alert('This mission is too important for me to allow you to jeopardize it.')
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 by including a script
tag inside of a datastar-patch-elements
SSE event.
If you only want to execute a script, you can append
the script tag to the body
.
Most SDKs have an ExecuteScript
helper function for executing a script. Here’s the code to generate the SSE event above using the Go SDK.
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.
Backend Requests
Between attributes and actions, Datastar provides you with everything you need to build hypermedia-driven applications. Using this approach, the backend drives state to the frontend and acts as the single source of truth, determining what actions the user can take next.
Sending Signals #
By default, all signals (except for local signals whose keys begin with an underscore) are sent in an object with every backend request. When using a GET
request, the signals are sent as a datastar
query parameter, otherwise they are sent as a JSON body.
By sending all signals in every request, the backend has full access to the frontend state. This is by design. It is not recommended to send partial signals, but if you must, you can use the filterSignals
option to filter the signals sent to the backend.
Nesting Signals #
Signals can be nested, making it easier to target signals in a more granular way on the backend.
Using dot-notation:
1<div data-signals-foo.bar="1"></div>
Using object syntax:
1<div data-signals="{foo: {bar: 1}}"></div>
Using two-way binding:
1<input data-bind-foo.bar />
A practical use-case of nested signals is when you have repetition of state on a page. The following example tracks the open/closed state of a menu on both desktop and mobile devices, and the toggleAll() action to toggle the state of all menus at once.
Reading Signals #
To read signals from the backend, JSON decode the datastar
query param for GET
requests, and the request body for all other methods.
All SDKs provide a helper function to read signals. Here’s how you would read the nested signal foo.bar
from an incoming request.
1using StarFederation.Datastar.DependencyInjection;
2
3// Adds Datastar as a service
4builder.Services.AddDatastar();
5
6public record Signals
7{
8 [JsonPropertyName("foo")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
9 public FooSignals? Foo { get; set; } = null;
10
11 public record FooSignals
12 {
13 [JsonPropertyName("bar")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
14 public string? Bar { get; set; }
15 }
16}
17
18app.MapGet("/read-signals", async (IDatastarService datastarService) =>
19{
20 Signals? mySignals = await datastarService.ReadSignalsAsync<Signals>();
21 var bar = mySignals?.Foo?.Bar;
22});
1import ("github.com/starfederation/datastar-go/datastar")
2
3type Signals struct {
4 Foo struct {
5 Bar string `json:"bar"`
6 } `json:"foo"`
7}
8
9signals := &Signals{}
10if err := datastar.ReadSignals(request, signals); err != nil {
11 http.Error(w, err.Error(), http.StatusBadRequest)
12 return
13}
1@Serializable
2data class Signals(
3 val foo: String,
4)
5
6val jsonUnmarshaller: JsonUnmarshaller<Signals> = { json -> Json.decodeFromString(json) }
7
8val request: Request =
9 postRequest(
10 body =
11 """
12 {
13 "foo": "bar"
14 }
15 """.trimIndent(),
16 )
17
18val signals = readSignals(request, jsonUnmarshaller)
SSE Events #
Datastar can stream zero or more Server-Sent Events (SSE) from the web server to the browser. There’s no special backend plumbing required to use SSE, just some special syntax. Fortunately, SSE is straightforward and provides us with some advantages, in addition to allowing us to send multiple events in a single response (in contrast to sending text/html
or application/json
responses).
First, set up your backend in the language of your choice. Familiarize yourself with sending SSE events, or use one of the backend SDKs to get up and running even faster. We’re going to use the SDKs in the examples below, which set the appropriate headers and format the events for us.
The following code would exist in a controller action endpoint in your backend.
1;; Import the SDK's api and your adapter
2(require
3 '[starfederation.datastar.clojure.api :as d*]
4 '[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]])
5
6;; in a ring handler
7(defn handler [request]
8 ;; Create an SSE response
9 (->sse-response request
10 {on-open
11 (fn [sse]
12 ;; Patches elements into the DOM
13 (d*/patch-elements! sse
14 "<div id=\"question\">What do you put in a toaster?</div>")
15
16 ;; Patches signals
17 (d*/patch-signals! sse "{response: '', answer: 'bread'}"))}))
1using StarFederation.Datastar.DependencyInjection;
2
3// Adds Datastar as a service
4builder.Services.AddDatastar();
5
6app.MapGet("/", async (IDatastarService datastarService) =>
7{
8 // Patches elements into the DOM.
9 await datastarService.PatchElementsAsync(@"<div id=""question"">What do you put in a toaster?</div>");
10
11 // Patches signals.
12 await datastarService.PatchSignalsAsync(new { response = "", answer = "bread" });
13});
1import ("github.com/starfederation/datastar-go/datastar")
2
3// Creates a new `ServerSentEventGenerator` instance.
4sse := datastar.NewSSE(w,r)
5
6// Patches elements into the DOM.
7sse.PatchElements(
8 `<div id="question">What do you put in a toaster?</div>`
9)
10
11// Patches signals.
12sse.PatchSignals([]byte(`{response: '', answer: 'bread'}`))
1import starfederation.datastar.utils.ServerSentEventGenerator;
2
3// Creates a new `ServerSentEventGenerator` instance.
4AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response);
5ServerSentEventGenerator generator = new ServerSentEventGenerator(responseAdapter);
6
7// Patches elements into the DOM.
8generator.send(PatchElements.builder()
9 .data("<div id=\"question\">What do you put in a toaster?</div>")
10 .build()
11);
12
13// Patches signals.
14generator.send(PatchSignals.builder()
15 .data("{\"response\": \"\", \"answer\": \"\"}")
16 .build()
17);
1use starfederation\datastar\ServerSentEventGenerator;
2
3// Creates a new `ServerSentEventGenerator` instance.
4$sse = new ServerSentEventGenerator();
5
6// Patches elements into the DOM.
7$sse->patchElements(
8 '<div id="question">What do you put in a toaster?</div>'
9);
10
11// Patches signals.
12$sse->patchSignals(['response' => '', 'answer' => 'bread']);
1from datastar_py import ServerSentEventGenerator as SSE
2from datastar_py.litestar import DatastarResponse
3
4async def endpoint():
5 return DatastarResponse([
6 SSE.patch_elements('<div id="question">What do you put in a toaster?</div>'),
7 SSE.patch_signals({"response": "", "answer": "bread"})
8 ])
1require 'datastar'
2
3# Create a Datastar::Dispatcher instance
4
5datastar = Datastar.new(request:, response:)
6
7# In a Rack handler, you can instantiate from the Rack env
8# datastar = Datastar.from_rack_env(env)
9
10# Start a streaming response
11datastar.stream do |sse|
12 # Patches elements into the DOM
13 sse.patch_elements %(<div id="question">What do you put in a toaster?</div>)
14
15 # Patches signals
16 sse.patch_signals(response: '', answer: 'bread')
17end
1// Creates a new `ServerSentEventGenerator` instance (this also sends required headers)
2ServerSentEventGenerator.stream(req, res, (stream) => {
3 // Patches elements into the DOM.
4 stream.patchElements(`<div id="question">What do you put in a toaster?</div>`);
5
6 // Patches signals.
7 stream.patchSignals({'response': '', 'answer': 'bread'});
8});
The PatchElements()
function updates the provided HTML element into the DOM, replacing the element with id="question"
. An element with the ID question
must already exist in the DOM.
The PatchSignals()
function updates the response
and answer
signals into the frontend signals.
With our backend in place, we can now use the data-on-click
attribute to trigger the @get()
action, which sends a GET
request to the /actions/quiz
endpoint on the server when a button is clicked.
1<div
2 data-signals="{response: '', answer: ''}"
3 data-computed-correct="$response.toLowerCase() == $answer"
4>
5 <div id="question"></div>
6 <button data-on-click="@get('/actions/quiz')">Fetch a question</button>
7 <button
8 data-show="$answer != ''"
9 data-on-click="$response = prompt('Answer:') ?? ''"
10 >
11 BUZZ
12 </button>
13 <div data-show="$response != ''">
14 You answered “<span data-text="$response"></span>”.
15 <span data-show="$correct">That is correct ✅</span>
16 <span data-show="!$correct">
17 The correct answer is “<span data-text="$answer"></span>” 🤷
18 </span>
19 </div>
20</div>
Now when the Fetch a question
button is clicked, the server will respond with an event to modify the question
element in the DOM and an event to modify the response
and answer
signals. We’re driving state from the backend!
data-indicator
#
The data-indicator
attribute sets the value of a signal to true
while the request is in flight, otherwise false
. We can use this signal to show a loading indicator, which may be desirable for slower responses.
Backend Actions #
We’re not limited to sending just GET
requests. Datastar provides backend actions for each of the methods available: @get()
, @post()
, @put()
, @patch()
and @delete()
.
Here’s how we can send an answer to the server for processing, using a POST
request.
One of the benefits of using SSE is that we can send multiple events (patch elements and patch signals) in a single response.
1generator.send(PatchElements.builder()
2 .data("<div id=\"question\">...</div>")
3 .build()
4);
5generator.send(PatchElements.builder()
6 .data("<div id=\"instructions\">...</div>")
7 .build()
8);
9generator.send(PatchSignals.builder()
10 .data("{\"answer\": \"...\", \"prize\": \"...\"}")
11 .build()
12);
In addition to your browser’s dev tools, the Datastar Inspector can be used to monitor and inspect SSE events received by Datastar.
Read more about SSE events in the reference.
Congratulations #
You’ve actually read the entire guide! You should now know how to use Datastar to build reactive applications that communicate with the backend using backend requests and SSE events.
Feel free to dive into the reference and explore the examples next, to learn more about what you can do with Datastar.
Reference
Attributes
Data attributes are evaluated in the order they appear in the DOM, have special casing rules, can be aliased to avoid conflicts with other libraries, can contain Datastar expressions, and have runtime error handling.
The Datastar VSCode extension and IntelliJ plugin provide autocompletion for all available data-*
attributes.
data-attr
#
Sets the value of any HTML attribute to an expression, and keeps it in sync.
1<div data-attr-title="$foo"></div>
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<div data-attr="{title: $foo, disabled: $bar}"></div>
data-bind
#
Creates a signal (if one doesn’t already exist) and sets up two-way data binding between it and an element’s value. This means that the value of the element is updated when the signal changes, and the signal value is updated when the value of the element changes.
The data-bind
attribute can be placed on any HTML element on which data can be input or choices selected (input
, select
,textarea
elements, and web components). Event listeners are added for change
and input
events.
1<input data-bind-foo />
The signal name can be specified in the key (as above), or in the value (as below). This can be useful depending on the templating language you are using.
1<input data-bind="foo" />
The initial value of the signal is set to the value of the element, unless a signal has already been defined. So in the example below, $foo
is set to bar
.
1<input data-bind-foo value="bar" />
Whereas in the example below, $foo
inherits the value baz
of the predefined signal.
Predefined Signal Types
When you predefine a signal, its type is preserved during binding. Whenever the element’s value changes, the signal value is automatically converted to match the original type.
For example, in the code below, $foo
is set to the number 10
(not the string "10"
) when the option is selected.
In the same way, you can assign multiple input values to a single signal by predefining it as an array. In the example below, $foo
becomes ["bar", "baz"]
when both checkboxes are checked, and ["", ""]
when neither is checked.
Modifiers
Modifiers allow you to modify behavior when binding signals.
__case
– Converts the casing of the signal name..camel
– Camel case:mySignal
(default).kebab
– Kebab case:my-signal
.snake
– Snake case:my_signal
.pascal
– Pascal case:MySignal
1<input data-bind-my-signal__case.kebab />
data-class
#
Adds or removes a class to or from an element based on an expression.
1<div data-class-hidden="$foo"></div>
If the expression evaluates to true
, the hidden
class is added to the element; otherwise, it is removed.
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<div data-class="{hidden: $foo, 'font-bold': $bar}"></div>
Modifiers
Modifiers allow you to modify behavior when defining a class name.
__case
– Converts the casing of the class..camel
– Camel case:myClass
.kebab
– Kebab case:my-class
(default).snake
– Snake case:my_class
.pascal
– Pascal case:MyClass
1<div data-class-my-class__case.camel="$foo"></div>
data-computed
#
Creates a signal that is computed based on an expression. The computed signal is read-only, and its value is automatically updated when any signals in the expression are updated.
1<div data-computed-foo="$bar + $baz"></div>
Computed signals are useful for memoizing expressions containing other signals. Their values can be used in other expressions.
Computed signal expressions must not be used for performing actions (changing other signals, actions, JavaScript functions, etc.). If you need to perform an action in response to a signal change, use the data-effect
attribute.
Modifiers
Modifiers allow you to modify behavior when defining computed signals.
__case
– Converts the casing of the signal name..camel
– Camel case:mySignal
(default).kebab
– Kebab case:my-signal
.snake
– Snake case:my_signal
.pascal
– Pascal case:MySignal
1<div data-computed-my-signal__case.kebab="$bar + $baz"></div>
data-effect
#
Executes an expression on page load and whenever any signals in the expression change. This is useful for performing side effects, such as updating other signals, making requests to the backend, or manipulating the DOM.
1<div data-effect="$foo = $bar + $baz"></div>
data-ignore
#
Datastar walks the entire DOM and applies plugins to each element it encounters. It’s possible to tell Datastar to ignore an element and its descendants by placing a data-ignore
attribute on it. This can be useful for preventing naming conflicts with third-party libraries, or when you are unable to escape user input.
Modifiers
__self
– Only ignore the element itself, not its descendants.
data-ignore-morph
#
Similar to the data-ignore
attribute, the data-ignore-morph
attribute tells the PatchElements
watcher to skip processing an element and its children when morphing elements.
To remove thedata-ignore-morph
attribute from an element, simply patch the element with thedata-ignore-morph
attribute removed.
data-indicator
#
Creates a signal and sets its value to true
while a fetch request is in flight, otherwise false
. The signal can be used to show a loading indicator.
This can be useful for showing a loading spinner, disabling a button, etc.
The signal name can be specified in the key (as above), or in the value (as below). This can be useful depending on the templating language you are using.
1<button data-indicator="fetching"></button>
When using data-indicator
with a fetch request initiated in a data-on-load
attribute, you should ensure that the indicator signal is created before the fetch request is initialized.
1<div data-indicator-fetching data-on-load="@get('/endpoint')"></div>
Modifiers
Modifiers allow you to modify behavior when defining indicator signals.
__case
– Converts the casing of the signal name..camel
– Camel case:mySignal
(default).kebab
– Kebab case:my-signal
.snake
– Snake case:my_signal
.pascal
– Pascal case:MySignal
data-json-signals
#
Sets the text content of an element to a reactive JSON stringified version of signals. Useful when troubleshooting an issue.
You can optionally provide a filter object to include or exclude specific signals using regular expressions.
1<!-- Only show signals that include "user" in their path -->
2<pre data-json-signals="{include: /user/}"></pre>
3
4<!-- Show all signals except those ending with "temp" -->
5<pre data-json-signals="{exclude: /temp$/}"></pre>
6
7<!-- Combine include and exclude filters -->
8<pre data-json-signals="{include: /^app/, exclude: /password/}"></pre>
Modifiers
Modifiers allow you to modify the output format.
__terse
– Outputs a more compact JSON format without extra whitespace. Useful for displaying filtered data inline.
data-on
#
Attaches an event listener to an element, executing an expression whenever the event is triggered.
1<button data-on-click="$foo = ''">Reset</button>
An evt
variable that represents the event object is available in the expression.
1<div data-on-myevent="$foo = evt.detail"></div>
The data-on
attribute works with events and custom events. The data-on-submit
event listener prevents the default submission behavior of forms.
Modifiers
Modifiers allow you to modify behavior when events are triggered. Some modifiers have tags to further modify the behavior.
__once
* – Only trigger the event listener once.__passive
* – Do not callpreventDefault
on the event listener.__capture
* – Use a capture event listener.__case
– Converts the casing of the event..camel
– Camel case:myEvent
.kebab
– Kebab case:my-event
(default).snake
– Snake case:my_event
.pascal
– Pascal case:MyEvent
__delay
– Delay the event listener..500ms
– Delay for 500 milliseconds (accepts any integer)..1s
– Delay for 1 second (accepts any integer).
__debounce
– Debounce the event listener..500ms
– Debounce for 500 milliseconds (accepts any integer)..1s
– Debounce for 1 second (accepts any integer)..leading
– Debounce with leading edge..notrail
– Debounce without trailing edge.
__throttle
– Throttle the event listener..500ms
– Throttle for 500 milliseconds (accepts any integer)..1s
– Throttle for 1 second (accepts any integer)..noleading
– Throttle without leading edge..trail
– Throttle with trailing edge.
__viewtransition
– Wraps the expression indocument.startViewTransition()
when the View Transition API is available.__window
– Attaches the event listener to thewindow
element.__outside
– Triggers when the event is outside the element.__prevent
– CallspreventDefault
on the event listener.__stop
– CallsstopPropagation
on the event listener.
* Only works with built-in events.
data-on-intersect
#
Runs an expression when the element intersects with the viewport.
1<div data-on-intersect="$intersected = true"></div>
Modifiers
Modifiers allow you to modify the element intersection behavior and the timing of the event listener.
__once
– Only triggers the event once.__half
– Triggers when half of the element is visible.__full
– Triggers when the full element is visible.__delay
– Delay the event listener..500ms
– Delay for 500 milliseconds (accepts any integer)..1s
– Delay for 1 second (accepts any integer).
__debounce
– Debounce the event listener..500ms
– Debounce for 500 milliseconds (accepts any integer)..1s
– Debounce for 1 second (accepts any integer)..leading
– Debounce with leading edge..notrail
– Debounce without trailing edge.
__throttle
– Throttle the event listener..500ms
– Throttle for 500 milliseconds (accepts any integer)..1s
– Throttle for 1 second (accepts any integer)..noleading
– Throttle without leading edge..trail
– Throttle with trailing edge.
__viewtransition
– Wraps the expression indocument.startViewTransition()
when the View Transition API is available.
1<div data-on-intersect__once__full="$fullyIntersected = true"></div>
data-on-interval
#
Runs an expression at a regular interval. The interval duration defaults to one second and can be modified using the __duration
modifier.
1<div data-on-interval="$count++"></div>
Modifiers
Modifiers allow you to modify the interval duration.
__duration
– Sets the interval duration..500ms
– Interval duration of 500 milliseconds (accepts any integer)..1s
– Interval duration of 1 second (default)..leading
– Execute the first interval immediately.
__viewtransition
– Wraps the expression indocument.startViewTransition()
when the View Transition API is available.
1<div data-on-interval__duration.500ms="$count++"></div>
data-on-load
#
Runs an expression when the element attribute is loaded into the DOM.
The expression contained in the data-on-load
attribute is executed when the element attribute is loaded into the DOM. This can happen on page load, when an element is patched into the DOM, and any time the attribute is modified (via a backend action or otherwise).
1<div data-on-load="$count = 1"></div>
Modifiers
Modifiers allow you to add a delay to the event listener.
__delay
– Delay the event listener..500ms
– Delay for 500 milliseconds (accepts any integer)..1s
– Delay for 1 second (accepts any integer).
__viewtransition
– Wraps the expression indocument.startViewTransition()
when the View Transition API is available.
1<div data-on-load__delay.500ms="$count = 1"></div>
data-on-signal-patch
#
Runs an expression whenever one or more signals are patched. This is useful for tracking changes, updating computed values, or triggering side effects when data updates.
1<div data-on-signal-patch="console.log('A signal changed!')"></div>
The patch
variable is available in the expression and contains the signal patch details.
1<div data-on-signal-patch="console.log('Signal patch:', patch)"></div>
You can filter which signals to watch using the data-on-signal-patch-filter
attribute.
Modifiers
Modifiers allow you to modify the timing of the event listener.
__delay
– Delay the event listener..500ms
– Delay for 500 milliseconds (accepts any integer)..1s
– Delay for 1 second (accepts any integer).
__debounce
– Debounce the event listener..500ms
– Debounce for 500 milliseconds (accepts any integer)..1s
– Debounce for 1 second (accepts any integer)..leading
– Debounce with leading edge..notrail
– Debounce without trailing edge.
__throttle
– Throttle the event listener..500ms
– Throttle for 500 milliseconds (accepts any integer)..1s
– Throttle for 1 second (accepts any integer)..noleading
– Throttle without leading edge..trail
– Throttle with trailing edge.
1<div data-on-signal-patch__debounce.500ms="doSomething()"></div>
data-on-signal-patch-filter
#
Filters which signals to watch when using the data-on-signal-patch
attribute.
The data-on-signal-patch-filter
attribute accepts an object with include
and/or exclude
properties that are regular expressions.
1<!-- Only react to counter signal changes -->
2<div data-on-signal-patch-filter="{include: /^counter$/}"></div>
3
4<!-- React to all changes except those ending with "changes" -->
5<div data-on-signal-patch-filter="{exclude: /changes$/}"></div>
6
7<!-- Combine include and exclude filters -->
8<div data-on-signal-patch-filter="{include: /user/, exclude: /password/}"></div>
data-preserve-attr
#
Preserves the value of an attribute when morphing DOM elements.
You can preserve multiple attributes by separating them with a space.
data-ref
#
Creates a new signal that is a reference to the element on which the data attribute is placed.
1<div data-ref-foo></div>
The signal name can be specified in the key (as above), or in the value (as below). This can be useful depending on the templating language you are using.
1<div data-ref="foo"></div>
The signal value can then be used to reference the element.
1$foo is a reference to a <span data-text="$foo.tagName"></span> element
Modifiers
Modifiers allow you to modify behavior when defining references.
__case
– Converts the casing of the key..camel
– Camel case:myKey
.kebab
– Kebab case:my-key
(default).snake
– Snake case:my_key
.pascal
– Pascal case:MyKey
1<div data-ref-my-signal__case.kebab></div>
data-show
#
Shows or hides an element based on whether an expression evaluates to true
or false
. For anything with custom requirements, use data-class
instead.
1<div data-show="$foo"></div>
To prevent flickering of the element before Datastar has processed the DOM, you can add a display: none
style to the element to hide it initially.
1<div data-show="$foo" style="display: none"></div>
data-signals
#
Patches (adds, updates or removes) one or more signals into the existing signals. Values defined later in the DOM tree override those defined earlier.
1<div data-signals-foo="1"></div>
Signals can be nested using dot-notation.
1<div data-signals-foo.bar="1"></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: {bar: 1, baz: 2}}"></div>
The value above is written in JavaScript object notation, but JSON, which is a subset and which most templating languages have built-in support for, is also allowed.
Setting a signal’s value to null
will remove the signal.
1<div data-signals="{foo: null}"></div>
Keys used in data-signals-*
are converted to camel case, so the signal name mySignal
must be written as data-signals-my-signal
or data-signals="{mySignal: 1}"
.
Signals beginning with an underscore are not included in requests to the backend by default. You can opt to include them by modifying the value of the filterSignals
option.
Signal names cannot begin with nor contain a double underscore (__
), due to its use as a modifier delimiter.
Modifiers
Modifiers allow you to modify behavior when patching signals.
__case
– Converts the casing of the signal name..camel
– Camel case:mySignal
(default).kebab
– Kebab case:my-signal
.snake
– Snake case:my_signal
.pascal
– Pascal case:MySignal
__ifmissing
Only patches signals if their keys do not already exist. This is useful for setting defaults without overwriting existing values.
data-style
#
Sets the value of inline CSS styles on an element based on an expression, and keeps them in sync.
The data-style
attribute can also be used to set multiple style properties on an element using a set of key-value pairs, where the keys represent CSS property names and the values represent expressions.
Style properties can be specified in either camelCase (e.g., backgroundColor
) or kebab-case (e.g., background-color
). They will be automatically converted to the appropriate format.
Empty string, null
, undefined
, or false
values will restore the original inline style value if one existed, or remove the style property if there was no initial value. This allows you to use the logical AND operator (&&
) for conditional styles: $condition && 'value'
will apply the style when the condition is true and restore the original value when false.
1<!-- When $x is false, color remains red from inline style -->
2<div style="color: red;" data-style-color="$x && 'green'"></div>
3
4<!-- When $hiding is true, display becomes none; when false, reverts to flex from inline style -->
5<div style="display: flex;" data-style-display="$hiding && 'none'"></div>
The plugin tracks initial inline style values and restores them when data-style expressions become falsy or during cleanup. This ensures existing inline styles are preserved and only the dynamic changes are managed by Datastar.
data-text
#
Binds the text content of an element to an expression.
1<div data-text="$foo"></div>
Pro Attributes #
The Pro attributes add functionality to the free open source Datastar framework. These attributes are available under a commercial license that helps fund our open source work.
data-animate
#Pro
Allows you to animate element attributes over time. Animated attributes are updated reactively whenever signals used in the expression change.
data-custom-validity
#Pro
Allows you to add custom validity to an element using an expression. The expression must evaluate to a string that will be set as the custom validity message. If the string is empty, the input is considered valid. If the string is non-empty, the input is considered invalid and the string is used as the reported message.
data-on-raf
#Pro
Runs an expression on every requestAnimationFrame
event.
1<div data-on-raf="$count++"></div>
Modifiers
Modifiers allow you to modify the timing of the event listener.
__throttle
– Throttle the event listener..500ms
– Throttle for 500 milliseconds (accepts any integer)..1s
– Throttle for 1 second (accepts any integer)..noleading
– Throttle without leading edge..trail
– Throttle with trailing edge.
1<div data-on-raf__throttle.10ms="$count++"></div>
data-on-resize
#Pro
Runs an expression whenever an element’s dimensions change.
1<div data-on-resize="$count++"></div>
Modifiers
Modifiers allow you to modify the timing of the event listener.
__debounce
– Debounce the event listener..500ms
– Debounce for 500 milliseconds (accepts any integer)..1s
– Debounce for 1 second (accepts any integer)..leading
– Debounce with leading edge..notrail
– Debounce without trailing edge.
__throttle
– Throttle the event listener..500ms
– Throttle for 500 milliseconds (accepts any integer)..1s
– Throttle for 1 second (accepts any integer)..noleading
– Throttle without leading edge..trail
– Throttle with trailing edge.
1<div data-on-resize__debounce.10ms="$count++"></div>
data-persist
#Pro
Persists signals in local storage. This is useful for storing values between page loads.
1<div data-persist></div>
The signals to be persisted can be filtered by providing a value that is an object with include
and/or exclude
properties that are regular expressions.
1<div data-persist="{include: /foo/, exclude: /bar/}"></div>
You can use a custom storage key by adding it after data-persist-
. By default, signals are stored using the key datastar
.
1<div data-persist-mykey></div>
Modifiers
Modifiers allow you to modify the storage target.
__session
– Persists signals in session storage instead of local storage.
data-query-string
#Pro
Syncs query string params to signal values on page load, and syncs signal values to query string params on change.
1<div data-query-string></div>
The signals to be synced can be filtered by providing a value that is an object with include
and/or exclude
properties that are regular expressions.
1<div data-query-string="{include: /foo/, exclude: /bar/}"></div>
Modifiers
Modifiers allow you to enable history support.
__filter
– Filters out empty values when syncing signal values to query string params.__history
– Enables history support – each time a matching signal changes, a new entry is added to the browser’s history stack. Signal values are restored from the query string params on popstate events.
1<div data-query-string__filter__history></div>
data-replace-url
#Pro
Replaces the URL in the browser without reloading the page. The value can be a relative or absolute URL, and is an evaluated expression.
1<div data-replace-url="`/page${page}`"></div>
data-scroll-into-view
#Pro
Scrolls the element into view. Useful when updating the DOM from the backend, and you want to scroll to the new content.
1<div data-scroll-into-view></div>
Modifiers
Modifiers allow you to modify scrolling behavior.
__smooth
– Scrolling is animated smoothly.__instant
– Scrolling is instant.__auto
– Scrolling is determined by the computedscroll-behavior
CSS property.__hstart
– Scrolls to the left of the element.__hcenter
– Scrolls to the horizontal center of the element.__hend
– Scrolls to the right of the element.__hnearest
– Scrolls to the nearest horizontal edge of the element.__vstart
– Scrolls to the top of the element.__vcenter
– Scrolls to the vertical center of the element.__vend
– Scrolls to the bottom of the element.__vnearest
– Scrolls to the nearest vertical edge of the element.__focus
– Focuses the element after scrolling.
1<div data-scroll-into-view__smooth></div>
data-view-transition
#Pro
Sets the view-transition-name
style attribute explicitly.
1<div data-view-transition="$foo"></div>
Page level transitions are automatically handled by an injected meta tag. Inter-page elements are automatically transitioned if the View Transition API is available in the browser and useViewTransitions
is true
.
Attribute Order #
Elements are evaluated by walking the DOM in a depth-first manner, and attributes are processed in the order they appear in the element. This is important in some cases, such as when using data-indicator
with a fetch request initiated in a data-on-load
attribute, in which the indicator signal must be created before the fetch request is initialized.
1<div data-indicator-fetching data-on-load="@get('/endpoint')"></div>
Attribute Casing #
According to the HTML specification, all data-*
attributes (not Datastar the framework, but any time a data attribute appears in the DOM) are case in-sensitive, but are converted to camelCase
when accessed from JavaScript by Datastar.
Datastar handles casing of data attributes in two ways:
- The keys used in attributes that define signals (
data-signals-*
,data-computed-*
, etc.), are converted tocamelCase
. For example,data-signals-my-signal
defines a signal namedmySignal
. You would use the signal in a Datastar expression as$mySignal
. - The keys used by all other attributes are, by default, converted to
kebab-case
. For example,data-class-text-blue-700
adds or removes the classtext-blue-700
, anddata-on-rocket-launched
would react to the event namedrocket-launched
.
You can use the __case
modifier to convert between camelCase
, kebab-case
, snake_case
, and PascalCase
, or alternatively use object syntax when available.
For example, if listening for an event called widgetLoaded
, you would use data-on-widget-loaded__case.camel
.
Aliasing Attributes #
It is possible to alias data-*
attributes to a custom alias (data-foo-*
, for example) using the bundler. A custom alias should only be used if you have a conflict with a legacy library and data-ignore
cannot be used.
We maintain a data-star-*
aliased version that can be included as follows.
1<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar-aliased.js"></script>
Datastar Expressions #
Datastar expressions used in data-*
attributes can parse signals (prefixed with $
).
A variable el
is available in every Datastar expression, representing the element that the attribute exists on.
1<div id="bar" data-text="$foo + el.id"></div>
Read more about Datastar expressions in the guide.
Error Handling #
Datastar has built-in error handling and reporting for runtime errors. When a data attribute is used incorrectly, for example data-text-foo
, the following error message is logged to the browser console.
1Uncaught datastar runtime error: textKeyNotAllowed
2More info: https://data-star.dev/errors/runtime/text_key_not_allowed?metadata=%7B%22plugin%22%3A%7B%22name%22%3A%22text%22%2C%22type%22%3A%22attribute%22%7D%2C%22element%22%3A%7B%22id%22%3A%22%22%2C%22tag%22%3A%22DIV%22%7D%2C%22expression%22%3A%7B%22rawKey%22%3A%22textFoo%22%2C%22key%22%3A%22foo%22%2C%22value%22%3A%22%22%2C%22fnContent%22%3A%22%22%7D%7D
3Context: {
4 "plugin": {
5 "name": "text",
6 "type": "attribute"
7 },
8 "element": {
9 "id": "",
10 "tag": "DIV"
11 },
12 "expression": {
13 "rawKey": "textFoo",
14 "key": "foo",
15 "value": "",
16 "fnContent": ""
17 }
18}
The “More info” link takes you directly to a context-aware error page that explains error and provides correct sample usage. See the error page for the example above, and all available error messages in the sidebar menu.
Actions
Datastar provides actions that can be used in Datastar expressions.
The@
prefix designates actions that are safe to use in expressions. This is a security feature that prevents arbitrary JavaScript from being executed in the browser. Datastar usesFunction()
constructors to create and execute these actions in a secure and controlled sandboxed environment.
@peek()
#
@peek(callable: () => any)
Allows accessing signals without subscribing to their changes in expressions.
1<div data-text="$foo + @peek(() => $bar)"></div>
In the example above, the expression in the data-text
attribute will be re-evaluated whenever $foo
changes, but it will not be re-evaluated when $bar
changes, since it is evaluated inside the @peek()
action.
@setAll()
#
@setAll(value: any, filter?: {include: RegExp, exclude?: RegExp})
Sets the value of all matching signals (or all signals if no filter is used) to the expression provided in the first argument. The second argument is an optional filter object with an include
property that accepts a regular expression to match signal paths. You can optionally provide an exclude
property to exclude specific patterns.
The Datastar Inspector can be used to inspect and filter current signals and view signal patch events in real-time.
1<!-- Sets the `foo` signal only -->
2<div data-signals-foo="false">
3 <button data-on-click="@setAll(true, {include: /^foo$/})"></button>
4</div>
5
6<!-- Sets all signals starting with `user.` -->
7<div data-signals="{user: {name: '', nickname: ''}}">
8 <button data-on-click="@setAll('johnny', {include: /^user\./})"></button>
9</div>
10
11<!-- Sets all signals except those ending with `_temp` -->
12<div data-signals="{data: '', data_temp: '', info: '', info_temp: ''}">
13 <button data-on-click="@setAll('reset', {include: /.*/, exclude: /_temp$/})"></button>
14</div>
@toggleAll()
#
@toggleAll(filter?: {include: RegExp, exclude?: RegExp})
Toggles the boolean value of all matching signals (or all signals if no filter is used). The argument is an optional filter object with an include
property that accepts a regular expression to match signal paths. You can optionally provide an exclude
property to exclude specific patterns.
The Datastar Inspector can be used to inspect and filter current signals and view signal patch events in real-time.
1<!-- Toggles the `foo` signal only -->
2<div data-signals-foo="false">
3 <button data-on-click="@toggleAll({include: /^foo$/})"></button>
4</div>
5
6<!-- Toggles all signals starting with `is` -->
7<div data-signals="{isOpen: false, isActive: true, isEnabled: false}">
8 <button data-on-click="@toggleAll({include: /^is/})"></button>
9</div>
10
11<!-- Toggles signals starting with `settings.` -->
12<div data-signals="{settings: {darkMode: false, autoSave: true}}">
13 <button data-on-click="@toggleAll({include: /^settings\./})"></button>
14</div>
Backend Actions #
@get()
#
@get(uri: string, options={ })
Sends a GET
request to the backend using the Fetch API. The URI can be any valid endpoint and the response must contain zero or more Datastar SSE events.
1<button data-on-click="@get('/endpoint')"></button>
By default, requests are sent with a Datastar-Request: true
header, and a {datastar: *}
object containing all existing signals, except those beginning with an underscore. This behavior can be changed using the filterSignals
option, which allows you to include or exclude specific signals using regular expressions.
When using a get
request, the signals are sent as a query parameter, otherwise they are sent as a JSON body.
When a page is hidden (in a background tab, for example), the default behavior is for the SSE connection to be closed, and reopened when the page becomes visible again. To keep the connection open when the page is hidden, set the openWhenHidden
option to true
.
1<button data-on-click="@get('/endpoint', {openWhenHidden: true})"></button>
It’s possible to send form encoded requests by setting the contentType
option to form
. This sends requests using application/x-www-form-urlencoded
encoding.
1<button data-on-click="@get('/endpoint', {contentType: 'form'})"></button>
It’s also possible to send requests using multipart/form-data
encoding by specifying it in the form
element’s enctype
attribute. This should be used when uploading files. See the form data example.
@post()
#
@post(uri: string, options={ })
Works the same as @get()
but sends a POST
request to the backend.
1<button data-on-click="@post('/endpoint')"></button>
@put()
#
@put(uri: string, options={ })
Works the same as @get()
but sends a PUT
request to the backend.
1<button data-on-click="@put('/endpoint')"></button>
@patch()
#
@patch(uri: string, options={ })
Works the same as @get()
but sends a PATCH
request to the backend.
1<button data-on-click="@patch('/endpoint')"></button>
@delete()
#
@delete(uri: string, options={ })
Works the same as @get()
but sends a DELETE
request to the backend.
1<button data-on-click="@delete('/endpoint')"></button>
Options #
All of the actions above take a second argument of options.
contentType
– The type of content to send. A value ofjson
sends all signals in a JSON request. A value ofform
tells the action to look for the closest form to the element on which it is placed (unless aselector
option is provided), perform validation on the form elements, and send them to the backend using a form request (no signals are sent). Defaults tojson
.filterSignals
– A filter object with aninclude
property that accepts a regular expression to match signal paths (defaults to all signals:/.*/
), and an optionalexclude
property to exclude specific signal paths (defaults to all signals that do not have a_
prefix:/(^_|\._).*/
).The Datastar Inspector can be used to inspect and filter current signals and view signal patch events in real-time.
selector
– Optionally specifies a form to send when thecontentType
option is set toform
. If the value isnull
, the closest form is used. Defaults tonull
.headers
– An object containing headers to send with the request.openWhenHidden
– Whether to keep the connection open when the page is hidden. Useful for dashboards but can cause a drain on battery life and other resources when enabled. Defaults tofalse
.retryInterval
– The retry interval in milliseconds. Defaults to1000
(one second).retryScaler
– A numeric multiplier applied to scale retry wait times. Defaults to2
.retryMaxWaitMs
– The maximum allowable wait time in milliseconds between retries. Defaults to30000
(30 seconds).retryMaxCount
– The maximum number of retry attempts. Defaults to10
.requestCancellation
– Controls request cancellation behavior. Can be'auto'
(default, cancels existing requests on the same element),'disabled'
(allows concurrent requests), or anAbortController
instance for custom control. Defaults to'auto'
.
Request Cancellation #
By default, when a new fetch request is initiated on an element, any existing request on that same element is automatically cancelled. This prevents multiple concurrent requests from conflicting with each other and ensures clean state management.
For example, if a user rapidly clicks a button that triggers a backend action, only the most recent request will be processed:
This automatic cancellation happens at the element level, meaning requests on different elements can run concurrently without interfering with each other.
You can control this behavior using the requestCancellation
option:
1<!-- Allow concurrent requests (no automatic cancellation) -->
2<button data-on-click="@get('/endpoint', {requestCancellation: 'disabled'})">Allow Multiple</button>
3
4<!-- Custom abort controller for fine-grained control -->
5<div data-signals-controller="new AbortController()">
6 <button data-on-click="@get('/endpoint', {requestCancellation: $controller})">Start Request</button>
7 <button data-on-click="$controller.abort()">Cancel Request</button>
8</div>
Response Handling #
Backend actions automatically handle different response content types:
text/event-stream
– Standard SSE responses with Datastar SSE events.text/html
– HTML elements to patch into the DOM.application/json
– JSON encoded signals to patch.text/javascript
– JavaScript code to execute in the browser.
text/html
When returning HTML (text/html
), the server can optionally include the following response headers:
datastar-selector
– A CSS selector for the target elements to patchdatastar-mode
– How to patch the elements (outer
,inner
,remove
,replace
,prepend
,append
,before
,after
). Defaults toouter
.datastar-use-view-transition
– Whether to use the View Transition API when patching elements.
application/json
When returning JSON (application/json
), the server can optionally include the following response header:
datastar-only-if-missing
– If set totrue
, only patch signals that don’t already exist.
text/javascript
When returning JavaScript (text/javascript
), the server can optionally include the following response header:
datastar-script-attributes
– Sets the script element’s attributes using a JSON encoded string.
Events #
All of the actions above trigger datastar-fetch
events during the fetch request lifecycle. The event type determines the stage of the request.
started
– Triggered when the fetch request is started.finished
– Triggered when the fetch request is finished.error
– Triggered when the fetch request encounters an error.retrying
– Triggered when the fetch request is retrying.retries-failed
– Triggered when all fetch retries have failed.upload-progress
– Triggered during file uploads (see below).
Upload Progress #Pro
All backend actions (@get()
, @post()
, @put()
, @patch()
, @delete()
) automatically support file upload progress monitoring when:
- Using Datastar Pro (not available in the free version).
- The target URL uses the HTTPS protocol.
- The request body is FormData (multipart/form-data).
The HTTPS requirement exists due to browser security restrictions. Browsers only allow the duplex
option (required for ReadableStream uploads) on secure connections. For HTTP URLs or non-FormData requests, standard fetch is used without progress tracking.
When these conditions are met, the actions dispatch upload-progress
fetch events with:
progress
– Upload percentage (0-100) as a stringloaded
– Bytes uploaded so far as a stringtotal
– Total bytes to upload as a string
1<form enctype="multipart/form-data"
2 data-signals="{progress: 0, uploading: false}"
3 data-on-submit__prevent="@post('https://example.com/upload', {contentType: 'form'})"
4 data-on-datastar-fetch="
5 if (evt.detail.type !== 'upload-progress') return;
6
7 const {progress, loaded, total} = evt.detail.argsRaw;
8 $uploading = true;
9 $progress = Number(progress);
10
11 if ($progress >= 100) {
12 $uploading = false;
13 }
14 "
15>
16 <input type="file" name="files" multiple />
17 <button type="submit">Upload</button>
18 <progress data-show="$uploading" data-attr-value="$progress" max="100"></progress>
19</form>
Pro Actions #
@clipboard()
#Pro
@clipboard(text: string, isBase64?: boolean)
Copies the provided text to the clipboard. If the second parameter is true
, the text is treated as Base64 encoded, and is decoded before copying.
Base64 encoding is useful when copying content that contains special characters, quotes, or code fragments that might not be valid within HTML attributes. This prevents parsing errors and ensures the content is safely embedded in data-*
attributes.
@fit()
#Pro
@fit(v: number, oldMin: number, oldMax: number, newMin: number, newMax: number, shouldClamp=false, shouldRound=false)
Linearly interpolates a value from one range to another. This is useful for converting between different scales, such as mapping a slider value to a percentage or converting temperature units.
The optional shouldClamp
parameter ensures the result stays within the new range, and shouldRound
rounds the result to the nearest integer.
1<!-- Convert a 0-100 slider to 0-255 RGB value -->
2<div>
3 <input type="range" min="0" max="100" value="50" data-bind-slider-value>
4 <div data-computed-rgb-value="@fit($sliderValue, 0, 100, 0, 255)">
5 RGB Value: <span data-text="$rgbValue"></span>
6 </div>
7</div>
8
9<!-- Convert Celsius to Fahrenheit -->
10<div>
11 <input type="number" data-bind-celsius value="20" />
12 <div data-computed-fahrenheit="@fit($celsius, 0, 100, 32, 212)">
13 <span data-text="$celsius"></span>°C = <span data-text="$fahrenheit.toFixed(1)"></span>°F
14 </div>
15</div>
16
17<!-- Map mouse position to element opacity (clamped) -->
18<div
19 data-signals-mouse-x="0"
20 data-computed-opacity="@fit($mouseX, 0, window.innerWidth, 0, 1, true)"
21 data-on-mousemove__window="$mouseX = evt.clientX"
22 data-attr-style="'opacity: ' + $opacity"
23>
24 Move your mouse horizontally to change opacity
25</div>
Datastar Pro
Datastar Pro adds functionality to the free open source Datastar framework. All pro features are available under a commercial license that helps fund the open source work of Star Federation.
Pricing #
License #
Datastar Pro is a lifetime license at a one-time cost. You can use pro features in any number of projects, and you receive access to updates and any additional pro features that are released in future. By purchasing a license, you are directly supporting the development of the Datastar framework.
Licensing FAQs #
Which license do I need to purchase?
The license you require depends on the entity that will use it:
- The Solo license is intended for solo developers and freelancers, including small organisations with a single developer only.
- The Team license is intended for organisations with up to 25 employees.
- Any organisation with over 25 employees should contact us to arrange for an Enterprise license. Customers purchasing an Enterprise license can opt in to benefits such as developer training, setup and launch support, custom licensing, and custom payment options.
What restrictions are there on the use of Datastar Pro?
Datastar Pro may be used by the license holder only, with the following restrictions:
- Redistribution, sublicensing, or making the software available to third parties in any form, outside of an “end product”, is strictly prohibited.
- Making the software available in a public repo is a form of redistribution, and is strictly prohibited.
- Adding the software to an open-source project is a violation of the license, and is strictly prohibited.
- Source maps are not provided, and may not be used in non-development environments. This is to help protect against unintentional redistribution and piracy.
What constitutes allowed and prohibited redistribution of Datastar Pro?
Datastar Pro may be used by the license holder only, on an unlimited number of projects that the license holder is directly involved in.
- Datastar Pro may be distributed in “end products” only. In this context, an end product means a finished application, website, or service that uses Datastar Pro internally but does not expose it to others for development or reuse (something you build using Datastar Pro, not something that lets others build with it).
- Datastar Pro may not be distributed to third parties in a way that allows them to build using Datastar Pro, as if they themselves were license holders.
Can I use a subset of the features that come with Datastar Pro?
Absolutely! You can build a custom bundle that includes only the plugins you need using the bundler.
You can also use the Datastar Inspector and Stellar CSS (once available) with or without using the Pro plugins.
Can I upgrade a Solo license to a Team or Enterprise license?
Certainly! Contact us at [email protected] from the email address you used to purchase a license and we’ll send you a link to upgrade your license at a discounted price.
Pro Features #
Attributes #
data-animate
- Animates element attributes over time.data-custom-validity
- Adds custom validity to an element.data-on-raf
- Runs an expression on every animation frame.data-on-resize
- Runs an expression on element resize.data-persist
- Persists signals in local storage.data-query-string
- Syncs query string params with signal values.data-replace-url
- Replaces the URL in the browser.data-scroll-into-view
- Scrolls an element into view.data-view-transition
- Setsview-transition-name
styles.
Actions #
@clipboard()
- Copies text to the clipboard.@fit()
- Linear interpolates a value.
Events #
upload-progress
- Real-time upload progress monitoring for file uploads.
Bundler #
The bundler allows you to create custom bundles, including only the plugins you need. This can be useful when you only need a subset of the Datastar framework, or when you want to intentionally restrict what plugins your development team has access to.

The bundler also allows you to create a custom aliased bundle, in which the syntax of Datastar attributes becomes data-{alias}-*
. This can help avoid conflicts with other frameworks that use data attributes.
Datastar Inspector #
The Datastar Inspector is a debugging tool that allows you to inspect and filter current signals, and view signal patch events and SSE events in real-time.
Written as a web component, the Datastar Inspector is useful when developing Datastar applications, and can be used as a starting point for you to build your own custom debugging tools.




Stellar CSS #
Stellar CSS is a lightweight CSS framework that provides a configurable design system in the form of CSS variables with no build step. It replaces the need for frameworks like Tailwind CSS that tend to bloat markup with utility classes, and Open Props that are non-configurable.
Stellar CSS is still a work-in-progress (it’s being actively used on this site) and will become available as a pro feature in the future.
SSE Events
Responses to backend actions with a content type of text/event-stream
can contain zero or more Datastar SSE events.
The backend SDKs can handle the formatting of SSE events for you, or you can format them yourself.
Event Types #
datastar-patch-elements
#
Patches one or more elements in the DOM. By default, Datastar morphs elements by matching top-level elements based on their ID.
In the example above, the element <div id="foo">Hello world!</div>
will be morphed into the target element with ID foo
.
Be sure to place IDs on top-level elements to be morphed, as well as on elements within them that you’d like to preserve state on (event listeners, CSS transitions, etc.).
SVG morphing in Datastar requires special handling due to XML namespaces. See the SVG morphing example.
Additional data
lines can be added to the response to override the default behavior.
Key | Description |
---|---|
data: mode outer | Morphs the outer HTML of the elements. This is the default (and recommended) mode. |
data: mode inner | Morphs the inner HTML of the elements. |
data: mode replace | Replaces the outer HTML of the elements. |
data: mode prepend | Prepends the elements to the target’s children. |
data: mode append | Appends the elements to the target’s children. |
data: mode before | Inserts the elements before the target as siblings. |
data: mode after | Inserts the elements after the target as siblings. |
data: mode remove | Removes the target elements from DOM. |
data: selector #foo | Selects the target element of the patch using a CSS selector. Not required when using the outer or inner modes. |
data: useViewTransition true | Whether to use view transitions when patching elements. Defaults to false . |
data: elements | The HTML elements to patch. |
Elements can be removed using the remove
mode and providing either an explicit selector
or elements with an ID.
Elements can span multiple lines. Sample output showing non-default options:
datastar-patch-signals
#
Patches signals into the existing signals on the page. The onlyIfMissing
line determines whether to update each signal with the new value only if a signal with that name does not yet exist. The signals
line should be a valid data-signals
attribute.
Signals can be removed by setting their values to null
.
Sample output showing non-default options:
SDKs
Datastar provides backend SDKs that can (optionally) simplify the process of generating SSE events specific to Datastar.
If you’d like to contribute an SDK, please follow the Contribution Guidelines.
Clojure #
A Clojure SDK as well as helper libraries and adapter implementations.
Maintainer: Jeremy Schoffen
C# #
A C# (.NET) SDK for working with Datastar.
Maintainer: Greg H
Contributors: Ryan Riley
Go #
A Go SDK for working with Datastar.
Maintainer: Delaney Gillilan
Other examples: 1 App 5 Stacks ported to Go+Templ+Datastar
Java #
A Java SDK for working with Datastar.
Maintainer: mailq
Contributors: Peter Humulock, Tom D.
Kotlin #
A Kotlin SDK for working with Datastar.
Maintainer: GuillaumeTaffin
PHP #
A PHP SDK for working with Datastar.
Maintainer: Ben Croker
Craft CMS #
Integrates the Datastar framework with Craft CMS, allowing you to create reactive frontends driven by Twig templates (by PutYourLightsOn).
Laravel #
Integrates the Datastar hypermedia framework with Laravel, allowing you to create reactive frontends driven by Blade views or controllers (by PutYourLightsOn).
Python #
A Python SDK and a PyPI package (including support for most popular frameworks).
Maintainer: Felix Ingram
Contributors: Chase Sterling
Ruby #
A Ruby SDK for working with Datastar.
Maintainer: Ismael Celis
Rust #
A Rust SDK for working with Datastar.
Maintainer: Glen De Cauwsemaecker
Contributors: Johnathan Stevers
Rama #
Integrates Datastar with Rama, a Rust-based HTTP proxy (example).
TypeScript #
A TypeScript SDK with support for Node.js, Deno, and Bun.
Maintainer: Edu Wass
Contributors: Patrick Marchand
PocketPages #
Integrates the Datastar framework with PocketPages.
Security
Datastar expressions are strings that are evaluated in a sandboxed context. This means you can use JavaScript in Datastar expressions.
Escape User Input #
The golden rule of security is to never trust user input. This is especially true when using Datastar expressions, which can execute arbitrary JavaScript. When using Datastar expressions, you should always escape user input. This helps prevent, among other issues, Cross-Site Scripting (XSS) attacks.
Avoid Sensitive Data #
Keep in mind that signal values are visible in the source code in plain text, and can be modified by the user before being sent in requests. For this reason, you should avoid leaking sensitive data in signals and always implement backend validation.
Ignore Unsafe Input #
If, for some reason, you cannot escape unsafe user input, you should ignore it using the data-ignore
attribute. This tells Datastar to ignore an element and its descendants when processing DOM nodes.
Content Security Policy #
When using a Content Security Policy (CSP), unsafe-eval
must be allowed for scripts, since Datastar evaluates expressions using a Function()
constructor.