Display & Binding
Datastar provides attributes that connect your HTML elements to signals, creating a reactive interface that updates automatically when data changes. These attributes handle displaying signal values and binding form inputs to create two-way synchronization between your interface and application state.
Displaying Signal Values
data-text (Datastar)
The data-text attribute displays the value of a signal or expression as an element's text content:
<div data-signals="{username: 'John'}">
<p data-text="$username"></p>
<!-- Displays: John -->
</div>When the username signal changes, the paragraph automatically updates to show the new value. The $ prefix indicates you're referencing a signal.
Expressions
You can use JavaScript expressions, not just signal names:
<div data-signals="{count: 5}">
<p data-text="$count * 2"></p>
<!-- Displays: 10 -->
<p data-text="'Items: ' + $count"></p>
<!-- Displays: Items: 5 -->
</div>XSS Protection
data-text sets the textContent property, which means HTML tags are escaped automatically. This protects against XSS attacks:
<div data-signals="{message: '<script>alert(1)</script>'}">
<p data-text="$message"></p>
<!-- Displays the text literally, doesn't execute the script -->
</div>Two-Way Binding
data-bind (Datastar)
The data-bind attribute creates two-way binding between a form input and a signal. Changes in either direction sync automatically:
<div data-signals="{email: ''}">
<input type="text" data-bind="email" />
<p data-text="'You entered: ' + $email"></p>
</div>As you type in the input, the paragraph updates immediately. If you change the signal value programmatically or from the server, the input updates to match.
Signal Creation
If the signal doesn't exist, data-bind creates it automatically using the input's current value:
<!-- No data-signals needed -->
<input type="text" data-bind="username" value="John" />
<!-- Creates signal 'username' with initial value 'John' -->Text Inputs
For standard text inputs and textareas, binding synchronizes the value:
<div data-signals="{name: 'John', bio: ''}">
<input type="text" data-bind="name" />
<textarea data-bind="bio" rows="4"></textarea>
</div>Number Inputs
Number inputs automatically convert between strings and numbers:
<div data-signals="{age: 25}">
<input type="number" data-bind="age" />
<!-- Signal 'age' is a number, not a string -->
</div>Range inputs work the same way:
<div data-signals="{volume: 50}">
<input type="range" data-bind="volume" min="0" max="100" />
<p data-text="'Volume: ' + $volume"></p>
</div>Select Dropdowns
Select elements bind their selected value to the signal:
<div data-signals="{category: 'tech'}">
<select data-bind="category">
<option value="tech">Technology</option>
<option value="design">Design</option>
<option value="business">Business</option>
</select>
</div>Multiple Select
For multi-select dropdowns, the signal becomes an array:
<div data-signals="{tags: ['php', 'laravel']}">
<select data-bind="tags" multiple>
<option value="php">PHP</option>
<option value="laravel">Laravel</option>
<option value="javascript">JavaScript</option>
<option value="vue">Vue.js</option>
</select>
</div>Checkboxes
Checkbox binding depends on whether you're tracking a boolean or a value:
Boolean Checkboxes
<div data-signals="{newsletter: false}">
<label>
<input type="checkbox" data-bind="newsletter" />
Subscribe to newsletter
</label>
</div>When the checkbox is checked, newsletter becomes true. When unchecked, it becomes false.
Value-Based Checkboxes
<div data-signals="{interests: []}">
<label>
<input type="checkbox" data-bind="interests" value="coding" />
Coding
</label>
<label>
<input type="checkbox" data-bind="interests" value="design" />
Design
</label>
<label>
<input type="checkbox" data-bind="interests" value="writing" />
Writing
</label>
</div>The interests signal becomes an array of checked values: ['coding', 'design'].
Radio Buttons
Radio buttons share a signal name and bind to their value:
<div data-signals="{plan: 'free'}">
<label>
<input type="radio" data-bind="plan" value="free" />
Free Plan
</label>
<label>
<input type="radio" data-bind="plan" value="pro" />
Pro Plan
</label>
<label>
<input type="radio" data-bind="plan" value="enterprise" />
Enterprise Plan
</label>
<p data-text="'Selected: ' + $plan"></p>
</div>Datastar automatically sets the name attribute based on the signal name if you don't provide one.
File Inputs
File inputs are handled specially. When you select a file, Datastar encodes it as base64 and creates multiple related signals:
<div data-signals="{avatar: null}">
<input type="file" data-bind="avatar" accept="image/*" />
</div>After selecting a file, you get three signals:
avatar: Array of base64-encoded file contentsavatarMimes: Array of MIME typesavatarNames: Array of file names
<input type="file" data-bind="document" />
<!-- After selecting "report.pdf", you have: -->
<!-- $document = ['base64encodedcontent...'] -->
<!-- $documentMimes = ['application/pdf'] -->
<!-- $documentNames = ['report.pdf'] -->For displaying uploaded images, see the File Uploads section.
Dynamic Attributes
data-attr:* (Datastar)
The data-attr:* attributes set HTML attributes dynamically based on signal values. Replace * with any attribute name:
<div data-signals="{
imageUrl: '/profile.jpg',
userName: 'John Doe',
isDisabled: false
}">
<img data-attr:src="$imageUrl" data-attr:alt="$userName" />
<button data-attr:disabled="$isDisabled">
Submit
</button>
</div>Common Uses
Links
<div data-signals="{userId: 42}">
<a data-attr:href="'/users/' + $userId">View Profile</a>
</div>Images
<div data-signals="{avatar: '/default.jpg'}">
<img data-attr:src="$avatar" alt="User avatar" />
</div>Form Elements
<div data-signals="{
isProcessing: false,
inputPlaceholder: 'Enter your email'
}">
<input data-attr:disabled="$isProcessing"
data-attr:placeholder="$inputPlaceholder" />
</div>Data Attributes
<div data-signals="{todoId: 123}">
<div data-attr:data-todo-id="$todoId">
Todo item content
</div>
</div>Common Patterns
Form with Live Preview
<div data-signals="{
title: '',
description: '',
price: 0
}">
<form>
<input type="text" data-bind="title" placeholder="Product Title" />
<textarea data-bind="description" placeholder="Description"></textarea>
<input type="number" data-bind="price" placeholder="Price" />
</form>
<div class="preview">
<h3 data-text="$title || 'Untitled'"></h3>
<p data-text="$description || 'No description'"></p>
<p data-text="'$' + $price"></p>
</div>
</div>Multiple Inputs Bound to One Signal
You can bind multiple inputs to the same signal. They all stay synchronized:
<div data-signals="{searchQuery: ''}">
<input data-bind="searchQuery" placeholder="Search in header..." />
<div class="sidebar">
<input data-bind="searchQuery" placeholder="Search in sidebar..." />
</div>
<p data-text="'Searching for: ' + $searchQuery"></p>
</div>Dynamic Link States
<div data-signals="{
currentPage: 'home',
docsUrl: '/docs/introduction'
}">
<a data-attr:href="$docsUrl"
data-attr:class="$currentPage === 'docs' ? 'active' : ''">
Documentation
</a>
</div>Learn More
- Styling - Control classes and styles reactively
- Events - Handle user interactions
- Forms - Build complete forms with validation
- Datastar Documentation - Complete reference for all Datastar attributes

