Skip to content

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:

blade
<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:

blade
<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:

blade
<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:

blade
<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:

blade
<!-- 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:

blade
<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:

blade
<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:

blade
<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:

blade
<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:

blade
<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

blade
<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

blade
<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:

blade
<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:

blade
<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 contents
  • avatarMimes: Array of MIME types
  • avatarNames: Array of file names
blade
<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:

blade
<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

blade
<div data-signals="{userId: 42}">
    <a data-attr:href="'/users/' + $userId">View Profile</a>
</div>

Images

blade
<div data-signals="{avatar: '/default.jpg'}">
    <img data-attr:src="$avatar" alt="User avatar" />
</div>

Form Elements

blade
<div data-signals="{
    isProcessing: false,
    inputPlaceholder: 'Enter your email'
}">
    <input data-attr:disabled="$isProcessing"
           data-attr:placeholder="$inputPlaceholder" />
</div>

Data Attributes

blade
<div data-signals="{todoId: 123}">
    <div data-attr:data-todo-id="$todoId">
        Todo item content
    </div>
</div>

Common Patterns

Form with Live Preview

blade
<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:

blade
<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>
blade
<div data-signals="{
    currentPage: 'home',
    docsUrl: '/docs/introduction'
}">
    <a data-attr:href="$docsUrl"
       data-attr:class="$currentPage === 'docs' ? 'active' : ''">
        Documentation
    </a>
</div>

Learn More