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>data-html (Hyper)
The data-html attribute displays the value of a signal or expression as HTML content, rendering tags instead of escaping them:
<div data-signals="{content: '<strong>Bold</strong> and <em>italic</em>'}">
<div data-html="$content"></div>
<!-- Renders: Bold and italic (with actual formatting) -->
</div>This is useful for rendering rich text content, markdown output, or server-generated HTML.
Dynamic HTML Updates
When the signal changes, the HTML re-renders automatically:
<div data-signals="{
items: ['Apple', 'Banana', 'Cherry'],
listHtml: ''
}" data-computed="$listHtml = '<ul>' + $items.map(i => '<li>' + i + '</li>').join('') + '</ul>'">
<div data-html="$listHtml"></div>
</div>Server-Generated Content
A common pattern is rendering HTML from the server:
<div data-signals="{preview: ''}">
<textarea data-bind="markdown" placeholder="Write markdown..."></textarea>
<button data-on:click="@post('/preview-markdown')">Preview</button>
<div data-html="$preview" class="prose"></div>
</div>// Controller
public function previewMarkdown()
{
$markdown = signals('markdown');
return hyper()->signals([
'preview' => Str::markdown($markdown)
]);
}Security Warning
data-html renders content without escaping, which can expose your application to XSS attacks if the content comes from untrusted sources. Always sanitize user-generated HTML before rendering:
// Always sanitize untrusted content
$safeHtml = strip_tags($userContent, '<p><strong><em><a>');Use data-text instead when displaying user input that shouldn't contain HTML.
Comparison: data-text vs data-html
| Attribute | Behavior | Use Case |
|---|---|---|
data-text | Escapes HTML tags | User input, plain text |
data-html | Renders HTML tags | Rich content, markdown, trusted HTML |
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>Preventing Flash of Unstyled Content
data-cloak (Hyper)
The data-cloak attribute prevents Flash of Unstyled Content (FOUC) by hiding elements until the framework initializes. This is essential for reactive content that shows template syntax before JavaScript loads.
Setup
First, add this CSS rule to your stylesheet (or in a <style> tag in your layout):
[data-cloak] { display: none !important; }Then add data-cloak to containers that have reactive content:
<div data-signals="{show: true}" data-cloak>
<div data-show="$show">
This won't flash on page load
</div>
</div>When Datastar initializes, it automatically removes the data-cloak attribute, making the content visible with proper state applied.
Without data-cloak (Problem)
<!-- BAD: User sees raw template briefly -->
<p data-text="$username">Loading...</p>
<!-- Shows "Loading..." then flashes to actual username -->With data-cloak (Solution)
<!-- GOOD: Content hidden until ready -->
<div data-cloak>
<p data-text="$username">Loading...</p>
</div>
<!-- User sees nothing, then sees final rendered content -->Common Patterns
Hide entire sections with conditional content:
<div data-signals="{loaded: false, items: []}" data-cloak>
<div data-show="$loaded">
<template data-for="item in $items">
<div data-text="item.name"></div>
</template>
</div>
<div data-show="!$loaded">
<p>Loading...</p>
</div>
</div>TIP
Add data-cloak to the outermost container that has reactive bindings. You don't need it on every element—just the parent container is sufficient.
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, styles, and transitions reactively
- Events - Handle user interactions
- Forms - Build complete forms with validation
- Teleport - Render content in different DOM locations
- Datastar Documentation - Complete reference for all Datastar attributes
