Skip to content

Core Concepts

Now that you've built your first Hyper application, let's consolidate your understanding by exploring the fundamental concepts that make Hyper work. This guide will help you develop a mental model for building reactive Laravel applications.

Signals: The Foundation of Reactivity

Signals are reactive variables that automatically update your user interface when their values change. Think of signals as the single source of truth for your application's state.

How Signals Work (Datastar)

When you create a signal in your HTML:

blade
<div data-signals="{count: 0}">
    <span data-text="$count"></span>
</div>

Datastar creates a reactive variable called count with an initial value of 0. Any element that references this signal (using the $ prefix) automatically updates when the signal's value changes.

This reactivity is powered by Datastar's signal system. When you write $count = $count + 1, Datastar:

  1. Updates the signal's value in its internal store
  2. Identifies all elements watching that signal
  3. Automatically updates those elements with the new value

The $ prefix tells Datastar you're referencing a signal, not a regular JavaScript variable.

Signal Scope (Datastar)

Signals are globally scoped once created. This means a signal created anywhere on the page is accessible everywhere on that page:

blade
<!-- Signal created in one div -->
<div data-signals="{username: 'John'}">
    <p data-text="$username"></p>
</div>

<!-- Same signal accessible in another div -->
<div>
    <p>Welcome back, <span data-text="$username"></span>!</p>
</div>

Both elements will display "John" and update together if username changes. This global scope makes it easy to share state across your interface without prop drilling or complex state management.

Types of Signals

Hyper and Datastar support three types of signals, each with different behavior:

Regular Signals (Datastar)

Regular signals are sent to the server with every request:

blade
<div data-signals="{count: 0, username: 'John'}">
    <!-- These signals are sent to the server -->
</div>

When you make a request using @postx, @patchx, or other HTTP actions, Datastar automatically includes all regular signals in the request payload. Your Laravel controller receives them and can access them using signals('count').

Local Signals (Datastar)

Local signals (prefixed with _) stay in the frontend and are never sent to the server:

blade
<div data-signals="{count: 0, _showDetails: false}">
    <!-- count is sent to server, _showDetails stays in browser -->
</div>

Use local signals for UI state that doesn't need server processing:

  • Accordion open/closed states
  • Which tab is currently active
  • Whether a dropdown menu is visible
  • Form field focus states

Local signals improve performance by reducing the data sent over the network. However, the server can still update local signals using hyper()->signals(['_showDetails' => true]) if needed.

Locked Signals (Hyper Extension)

Locked signals (suffixed with _) are sent to the server and validated to detect tampering. Important: Locked signals can only be created using the @signals directive, not data-signals:

blade
<div @signals(['userId_' => 123, 'role_' => 'admin'])>
    <!-- These signals are protected from client-side modification -->
</div>

When you create locked signals with @signals, Hyper:

  1. Stores their values securely in the session (encrypted)
  2. Validates they haven't changed on subsequent requests
  3. Throws a HyperSignalTamperedException if tampering is detected

This security only works through the @signals directive because it hooks into the PHP-side processing. Using data-signals with the _ suffix won't provide this protection.

Use locked signals for:

  • User IDs and authentication data
  • Pricing information
  • Permission levels
  • Any data that should only change through server actions

Security Deep Dive

Locked signals provide tamper protection for sensitive data. Learn the complete security model and session storage mechanism.

The Request Cycle

Understanding how data flows between your frontend and Laravel helps you build applications confidently.

Frontend to Server

When a user interacts with your application:

  1. User Action: User clicks a button with data-on:click="@postx('/increment')"
  2. Signal Collection: Datastar gathers all regular signals (not local _ signals)
  3. Request: Datastar sends a POST request to /increment with signals as JSON payload
  4. CSRF Token: Hyper's @postx automatically includes the CSRF token in headers

The request looks like this:

http
POST /increment HTTP/1.1
X-CSRF-TOKEN: abc123...
Content-Type: application/json

{
  "count": 5,
  "errors": []
}

Server Processing

In your Laravel controller:

php
public function increment()
{
    // Read signals sent from frontend
    $count = signals('count', 0);

    // Process business logic
    $newCount = $count + 1;

    // Return updates
    return hyper()->signals(['count' => $newCount]);
}

The signals() helper (provided by Hyper) reads signals from the request, similar to how request() reads form data.

Server to Frontend

Hyper returns Server-Sent Events (SSE) that tell Datastar what to update:

http
HTTP/1.1 200 OK
Content-Type: text/event-stream

event: datastar-signal
data: {"count": 6}

Datastar receives this event and automatically updates the count signal. Any element displaying $count updates instantly.

Multiple Updates

You can chain multiple operations in a single response:

php
return hyper()
    ->signals(['count' => 6, 'errors' => []])
    ->view('counter-status', $data)
    ->js('console.log("Updated!")');

This sends multiple SSE events:

  1. Update signals
  2. Replace DOM elements
  3. Execute JavaScript

All operations happen atomically—the user sees a complete, consistent update.

Datastar Attributes vs Hyper Attributes

Understanding which attributes come from Datastar and which are Hyper extensions helps you understand where to look for documentation and what each tool provides.

Datastar Attributes

These attributes are part of Datastar and handle core reactivity:

State Management:

  • data-signals: Create reactive signals
  • data-computed: Create computed values from signals
  • data-effect: Run code when signals change

Display & Binding:

  • data-text: Display signal values as text
  • data-bind: Two-way bind input values to signals
  • data-show: Show/hide elements based on conditions
  • data-class: Toggle CSS classes reactively
  • data-style: Apply styles reactively
  • data-attr: Set attributes reactively

Events:

  • data-on:click: Handle click events
  • data-on:[event]: Handle any DOM event
  • data-init: Run code when element loads
  • data-on-interval: Run code on a timer

Backend Communication:

  • @get, @post, @put, @patch, @delete: HTTP actions
  • @fetch: Custom fetch requests

References:

  • data-ref: Create references to DOM elements
  • data-intersect: React to element visibility

Learn more about Datastar attributes in the Datastar Attributes reference.

Hyper Attributes

These attributes are Hyper's Laravel-specific extensions:

Enhanced Backend Actions:

  • @postx, @putx, @patchx, @deletex: HTTP actions with automatic CSRF tokens

Laravel Integration:

  • data-error: Display Laravel validation errors for specific fields
  • data-navigate: Client-side navigation with Laravel routes
  • data-if: Conditional rendering (Hyper's optimization)
  • data-for: Loop rendering (Hyper's optimization)

Learn more in the Hyper Attributes reference.

Blade Directives

Hyper provides several Blade directives that make working with signals and fragments more natural in Laravel.

@hyper

Includes Hyper's JavaScript and CSRF token:

blade
<head>
    @hyper
</head>

Expands to:

html
<meta name="csrf-token" content="your-token">
<script type="module" src="/vendor/hyper/js/hyper.js"></script>

@signals

Initialize signals from PHP data:

blade
<div @signals(['count' => $initialCount, 'max' => 100])>
    <!-- Signals are now available -->
</div>

This is cleaner than manually writing JSON and handles encoding automatically. You can use the spread operator to expand arrays:

blade
<div @signals(...$userData, ['_localState' => false])>
    <!-- All $userData keys become signals, plus _localState -->
</div>

@fragment / @endfragment

Define reusable sections for partial updates:

blade
@fragment('user-stats')
    <div id="stats">
        <p>Posts: {{ $postCount }}</p>
        <p>Followers: {{ $followerCount }}</p>
    </div>
@endfragment

From your controller, render just this fragment:

php
return hyper()->fragment('users.profile', 'user-stats', [
    'postCount' => 42,
    'followerCount' => 1337
]);

Fragments help you avoid creating separate component files for small, related sections. Learn more in Backend Patterns: Fragments.

Helpers

Hyper provides two main helpers that mirror Laravel's familiar patterns.

signals()

Read signals from requests, similar to request():

php
// Get a specific signal with default
$count = signals('count', 0);

// Get all signals
$allSignals = signals()->all();

// Check if a signal exists
if (signals()->has('username')) {
    // ...
}

// Validate signals (returns validated data or throws exception)
$validated = signals()->validate([
    'email' => 'required|email',
    'age' => 'integer|min:18'
]);

hyper()

Build reactive responses, similar to response():

php
// Update signals
return hyper()->signals(['count' => 5]);

// Render a view (targets by ID)
return hyper()->view('counter-status', $data);

// Render a fragment
return hyper()->fragment('users.profile', 'stats', $data);

// Manipulate DOM
return hyper()
    ->append('#list', view('item', $item))
    ->remove('#old-item')
    ->html('<p>New content</p>', ['selector' => '#target']);

// Execute JavaScript
return hyper()->js('alert("Done!")');

// Chain multiple operations
return hyper()
    ->signals(['count' => 5, 'errors' => []])
    ->view('status', $data)
    ->when($milestone, fn($h) => $h->js('celebrate()'));

The fluent interface lets you compose complex responses by chaining methods.

Validation

Hyper integrates Laravel's validation system with signals, making validation feel natural.

Validating Signals

Use signals()->validate() exactly like request()->validate():

php
public function store()
{
    $validated = signals()->validate([
        'title' => 'required|string|max:255',
        'email' => 'required|email',
        'age' => 'integer|min:18'
    ]);

    // Use validated data...
}

If validation fails, Hyper automatically:

  1. Creates an errors signal with Laravel's error structure
  2. Returns it to the frontend
  3. Displays errors using data-error attributes

Displaying Errors

Use the data-error attribute (Hyper extension) to display validation errors:

blade
<div data-signals="{title: '', errors: []}">
    <input data-bind="title" />
    <div data-error="title"></div>
</div>

When validation fails, data-error="title" automatically shows the first error message for the title field. When validation passes or you clear errors, the element becomes hidden.

Complete Validation Guide

Laravel's entire validation system works seamlessly with Hyper, including custom rules, file validation, and real-time validation. See Essentials: Validation for comprehensive examples and patterns.

Mental Model

Here's how to think about Hyper applications:

  1. Signals are your state: All dynamic data lives in signals
  2. Datastar provides reactivity: Changes to signals automatically update the UI
  3. Laravel processes logic: Controllers handle business rules, validation, and data
  4. Hyper bridges the gap: Helpers and directives make Laravel and Datastar work together seamlessly
  5. SSE delivers updates: Server-Sent Events push changes to the frontend instantly

You write Blade templates with reactive attributes, handle interactions in Laravel controllers, and let Hyper manage the communication. The result feels like a single-page application but maintains Laravel's server-driven architecture.

Common Patterns

Form Submission

blade
<form data-on:submit__prevent="@postx('/contacts')">
    <input data-bind="name" />
    <div data-error="name"></div>
    <button type="submit">Save</button>
</form>
php
public function store()
{
    $validated = signals()->validate([
        'name' => 'required|string|max:255'
    ]);

    Contact::create($validated);

    return hyper()
        ->signals(['name' => '', 'errors' => []])
        ->js("showToast('Contact created!')");
}

Inline Editing

blade
<div id="todo-{{ $todo->id }}">
    @if($editing)
        <input data-bind="title" />
        <button data-on:click="@postx('/todos/{{ $todo->id }}')">Save</button>
    @else
        <span data-text="$title"></span>
        <button data-on:click="$editing = true">Edit</button>
    @endif
</div>
blade
<input data-bind="search"
       data-on:input="@get('/search')" />

<div id="results">
    <!-- Results updated by server -->
</div>
php
public function search()
{
    $results = User::where('name', 'like', '%' . signals('search') . '%')
        ->limit(10)
        ->get();

    return hyper()->view('search-results', compact('results'));
}

What's Next?

You now understand Hyper's core concepts. Continue learning with:

Each section builds on these concepts, showing you practical patterns for building real applications.