Skip to content

Your First Hyper Application

Let's build a counter application from scratch. This hands-on tutorial will teach you Hyper's fundamentals by progressively adding reactivity to a simple app.

What We'll Build

A counter with these features:

  • Increment and decrement buttons
  • Display current count
  • Server-side logic and validation
  • Real-time UI updates without page reloads
  • Status indicators based on count value

By the end, you'll understand how Hyper makes Laravel applications reactive.

Step 1: The Starting Point - Pure HTML

Let's begin with the simplest possible counter—just HTML with no functionality.

Create a route in routes/web.php:

php
use Illuminate\Support\Facades\Route;

Route::get('/counter', function () {
    return view('counter');
});

Create the view in resources/views/counter.blade.php:

blade
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Counter App</title>
</head>
<body>
    <div>
        <button>-</button>
        <span>0</span>
        <button>+</button>
    </div>
</body>
</html>

Try it: Visit /counter in your browser. You'll see three elements: a minus button, the number 0, and a plus button. The buttons don't do anything yet—they're just static HTML.

Our Goal: Make clicking "+" increase the number by one, and clicking "-" decrease it by one.

Step 2: Installing Hyper

Now let's add the tools we need.

Install the package (if you haven't already):

bash
composer require dancycodes/hyper
php artisan vendor:publish --tag=hyper-assets

Update your counter view to include Hyper:

blade
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Counter App</title>
    {{-- Include Hyper (Datastar + Hyper enhancements) --}}
    @hyper
</head>
<body>
    <div>
        <button>-</button>
        <span>0</span>
        <button>+</button>
    </div>
</body>
</html>

What @hyper Does

If you view the page source, you'll see @hyper generated:

  1. CSRF Meta Tag: <meta name="csrf-token" content="...">
  2. Script Tag: <script type="module" src="/vendor/hyper/js/hyper.js"></script>

This loads Datastar (the reactive framework) plus Hyper's Laravel extensions.

Your counter still looks the same and buttons don't work. That's expected—we've just installed the tools. Now let's use them.

Step 3: Adding Frontend Reactivity

Let's make the counter work entirely in the frontend first. This teaches fundamental concepts before involving the server.

Understanding Signals (Datastar Concept):

A "signal" is a reactive variable that the UI watches. When the signal's value changes, any UI element bound to it automatically updates. Think of it like a spreadsheet—when you change a cell, formulas referencing it recalculate automatically.

Update your view to add reactivity:

blade
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Counter App</title>
    @hyper
</head>
<body>
    <div data-signals="{count: 0}">
        <button data-on:click="$count = $count - 1">-</button>
        <span data-text="$count"></span>
        <button data-on:click="$count = $count + 1">+</button>
    </div>
</body>
</html>

Try it: Click the buttons! The counter now works.

What's happening:

  1. data-signals="{count: 0}" - Creates a reactive signal named count with initial value 0 (Datastar)
  2. data-on:click="$count = $count + 1" - When button is clicked, increase count (Datastar)
  3. data-text="$count" - Display the current value of count (Datastar)
  4. $count - The $ prefix accesses the signal value (Datastar)

Datastar Reactivity

All the reactivity here is powered by Datastar. The data-* attributes, signals, and automatic UI updates are Datastar features. Hyper hasn't done anything yet—we're using pure Datastar.

Key Insight: The signal count is reactive. When you update it ($count = $count + 1), Datastar automatically updates every element displaying that signal (data-text="$count").

Step 4: Moving to the Server

The frontend counter works, but as Laravel developers, we often want logic on the server for business rules, validation, persistence, and testing. Let's move our increment/decrement logic to Laravel controllers.

Create a controller:

bash
php artisan make:controller CounterController

Add the show and increment methods:

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class CounterController extends Controller
{
    public function show()
    {
        return view('counter');
    }

    public function increment()
    {
        // First, let's verify our route is being called
        dd("increment called - route is working!");
    }
}

Update routes to use the controller:

php
use App\Http\Controllers\CounterController;

Route::get('/counter', [CounterController::class, 'show']);
Route::post('/counter/increment', [CounterController::class, 'increment']);

Try to call the Laravel route from your frontend:

blade
<div data-signals="{count: 0}">
    <button data-on:click="$count = $count - 1">-</button>
    <span data-text="$count"></span>
    {{-- Try to call our Laravel route --}}
    <button data-on:click="@post('/counter/increment')">+</button>
</div>

Try it: Click the + button and open your browser's Network tab. You'll see the request fails with a "419 Page Expired" error.

Common Mistake #1: CSRF Token Missing

This is the most common error for new Hyper users! Laravel protects all POST requests with CSRF tokens to prevent attacks. Datastar's @post() action doesn't include the CSRF token by default, so Laravel rejects it with a 419 error.

Solution: Always use Hyper's CSRF-protected actions for mutating operations: @postx(), @putx(), @patchx(), @deletex()

Solution: Use Hyper's Enhanced Actions

This is where Hyper's Laravel-specific enhancements come in. Replace @post with @postx:

blade
<div data-signals="{count: 0}">
    <button data-on:click="$count = $count - 1">-</button>
    <span data-text="$count"></span>
    {{-- Hyper's @postx automatically includes CSRF token --}}
    <button data-on:click="@postx('/counter/increment')">+</button>
</div>

Try the + button again. Now it works! Check the Network tab—the request succeeds and includes the X-CSRF-TOKEN header automatically. You should see Laravel's dd() output.

Hyper vs Pure Datastar

  • Pure Datastar: @post(), @get(), @put(), @patch(), @delete() - Standard HTTP actions
  • Hyper Enhanced: @postx(), @putx(), @patchx(), @deletex() - Same actions with automatic Laravel CSRF token injection

This is your first taste of how Hyper extends Datastar with Laravel-specific conveniences.

Step 5: Understanding Signal Transmission

When you click the + button, notice something important in the Network tab request payload: Datastar automatically sends ALL current signal values to your Laravel controller.

You'll see something like:

json
{
    "count": 0
}

This is automatic. Datastar sends every signal with every request. Your Laravel controllers always have access to the complete frontend state.

Let's use this in our controller:

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

    // Increment it
    $newCount = $currentCount + 1;

    // For now, just verify we're receiving it correctly
    dd("Current count from frontend: " . $currentCount);
}

Try clicking + and you'll see the current count value in the dump output.

The signals() Helper (Hyper)

signals() is a Hyper helper function that reads signals sent from the frontend. It works just like Laravel's request() helper:

  • signals('count') - Get the count signal
  • signals('count', 0) - Get count with default value
  • signals()->all() - Get all signals.

Step 6: Returning Signal Updates

Now let's make the server actually update the counter. We need to send the new count back to the frontend.

Update the increment method:

php
public function increment()
{
    $currentCount = signals('count', 0);
    $newCount = $currentCount + 1;

    // Return updated signal to frontend (Hyper helper)
    return hyper()->signals(['count' => $newCount]);
}

Try clicking + and watch the Network tab. You'll see a Server-Sent Event response:

event: datastar-patch-signals
data: signals {"count":1}

Clicking + sends the request, the server increments the count, and the UI updates automatically!

The hyper() Helper (Hyper)

hyper() is Hyper's fluent response builder, similar to Laravel's response() helper. It provides methods for:

  • Updating signals: hyper()->signals()
  • Rendering views: hyper()->view()
  • DOM manipulation: hyper()->html(), remove(), append(), etc.
  • Streaming: hyper()->stream()

Step 7: Adding the Decrement Action

Let's add the decrement functionality with server validation.

Add the decrement method to your controller:

php
public function decrement()
{
    $currentCount = signals('count', 0);
    $newCount = $currentCount - 1;

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

Add the route:

php
Route::post('/counter/decrement', [CounterController::class, 'decrement']);

Update the frontend:

blade
<div data-signals="{count: 0}">
    <button data-on:click="@postx('/counter/decrement')">-</button>
    <span data-text="$count"></span>
    <button data-on:click="@postx('/counter/increment')">+</button>
</div>

Try it: Both buttons now work, with all logic on the server!

Step 8: Adding Validation

Let's prevent the count from going negative using Laravel's validation.

Update the decrement method:

php
public function decrement()
{
    // Validate using Hyper's validation (extends Laravel validation)
    $validated = signals()->validate([
        'count' => 'required|integer|gt:0'
    ], [
        'count.gt' => 'The count cannot be negative!'
    ]);

    $currentCount = signals('count', 0);
    $newCount = $currentCount - 1;

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

Add an errors signal and error display:

blade
<div data-signals="{count: 0, errors: []}">
    <button data-on:click="@postx('/counter/decrement')">-</button>
    <span data-text="$count"></span>
    <button data-on:click="@postx('/counter/increment')">+</button>

    {{-- Display validation errors (Hyper's data-error attribute) --}}
    <div data-error="count" style="color: red;"></div>
</div>

Try it:

  1. Click - until count reaches 0
  2. Try clicking - again
  3. You'll see "The count cannot be negative!" appear

Hyper's Validation Integration

  • signals()->validate() - Works exactly like Laravel's $request->validate()
  • Validation errors automatically populate the errors signal
  • data-error="count" - Hyper's attribute that displays errors for the count field
  • When validation passes, errors are automatically cleared

How it works:

  1. signals()->validate() validates the signals
  2. If validation fails, it throws an exception and sends errors to frontend
  3. Hyper automatically updates the errors signal
  4. data-error="count" displays the error for the count field
  5. If validation passes, errors is cleared

Step 9: Local Signals

Sometimes you want signals that stay in the frontend and aren't sent to the server. Let's add a signal that tracks decrement attempts.

Add a local signal (underscore prefix):

blade
<div data-signals="{count: 0, errors: [], _attempts: 0}">
    {{-- Increment attempts when clicking decrement --}}
    <button data-on:click="@postx('/counter/decrement'); $_attempts = $_attempts + 1">-</button>
    <span data-text="$count"></span>
    <button data-on:click="@postx('/counter/increment')">+</button>

    <div data-error="count" style="color: red;"></div>

    {{-- Show attempts locally (not sent to server) --}}
    <div>Decrement attempts: <span data-text="$_attempts"></span></div>
</div>

Try it: Click the - button multiple times. Watch the Network tab—you'll see that only count and errors are sent to the server, while _attempts stays in the browser and updates reactively.

Signal Types (Datastar)

  • Regular signals (count) - Sent to server with every request
  • Local signals (_attempts) - Stay in browser, never sent to server (underscore prefix)
  • Locked signals (userId_) - Sent to server, validated for tampering (underscore suffix, Hyper feature)

Though local signals aren't sent to the server, they can still be updated from the server using hyper()->signals().

Step 10: Server-Initialized Signals

Instead of manually writing JSON for signals, use Hyper's @signals directive for a more Laravel-native approach.

Update your controller's show method:

php
public function show()
{
    $initialData = [
        'count' => 0,
        'max_value' => 100
    ];

    return view('counter', compact('initialData'));
}

Use the @signals directive in your view:

blade
<div @signals(...$initialData, ['_attempts' => 0])>
    <button data-on:click="@postx('/counter/decrement'); $_attempts = $_attempts + 1">-</button>
    <span data-text="$count"></span>
    <button data-on:click="@postx('/counter/increment')">+</button>

    <div data-error="count" style="color: red;"></div>

    {{-- Show the maximum allowed value from the server --}}
    <div>Maximum: <span data-text="$max_value"></span></div>
    <div>Decrement attempts: <span data-text="$_attempts"></span></div>
</div>

Understanding the Spread Operator

  • ... spreads the $initialData array into individual signals: count, max_value
  • Without spread, you'd get an initialData signal instead of individual signals
  • You can mix server data (spread from array) with frontend-only signals using array syntax

Benefits of @signals (Hyper)

  • Type-safe signal initialization from PHP variables
  • Automatic JSON encoding with proper escaping
  • Clear separation between backend data and frontend presentation
  • Easy to pass complex data structures (arrays, objects, models, collections)

Step 11: DOM Manipulation with Views

So far we've updated signal values. Now let's learn to update HTML elements themselves. We'll add a status message that changes based on the count value.

Add a status area to your view:

blade
<div @signals(...$initialData, ['_attempts' => 0])>
    <button data-on:click="@postx('/counter/decrement'); $_attempts = $_attempts + 1">-</button>
    <span data-text="$count"></span>
    <button data-on:click="@postx('/counter/increment')">+</button>

    <div data-error="count" style="color: red;"></div>

    {{-- Status area to be updated from server --}}
    <div id="status" style="padding: 10px; margin-top: 20px;">
        <p>Status will appear here</p>
    </div>
</div>

Create a status view in resources/views/counter-status.blade.php:

blade
<div id="status" style="padding: 10px; margin-top: 20px; {{ $statusStyle }}">
    <strong>{{ $statusText }}</strong>
</div>

Update the increment method to return HTML:

php
public function increment()
{
    $currentCount = signals('count', 0);
    $newCount = $currentCount + 1;

    // Determine status based on count
    if ($newCount <= 3) {
        $statusText = "Low ({$newCount})";
        $statusStyle = "background: #e3f2fd; color: #1976d2;";
    } elseif ($newCount <= 7) {
        $statusText = "Medium ({$newCount})";
        $statusStyle = "background: #fff3e0; color: #f57f17;";
    } else {
        $statusText = "High ({$newCount})";
        $statusStyle = "background: #ffebee; color: #d32f2f;";
    }

    return hyper()
        ->signals(['count' => $newCount, 'errors' => []])
        ->view('counter-status', compact('statusText', 'statusStyle'));
}

Try it: Click the increment button and watch the status area update with different colors based on the count!

Understanding hyper()->view() (Hyper)

  • Renders a Blade view with provided data
  • By default, targets elements with matching IDs (the #status div)
  • The entire element is replaced with the rendered view
  • Chain with ->signals() to update both signals and HTML
  • Uses full power of Blade templating for dynamic HTML

Step 12: Using Fragments for Code Reuse

Fragments let you define reusable sections within Blade views without creating separate files. The key insight is that fragments are about code reuse - you can render a fragment to update any part of your page using selector options.

Update your main view to include fragments:

blade
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Counter App</title>
    @hyper
</head>
<body>
    <div @signals(['count' => 0])>
        <h2>Counter with Fragments</h2>

        {{-- Button controls --}}
        <div id="counter-buttons">
            @fragment('button-controls')
                <button data-on:click="@postx('/counter/decrement')"
                        {{ $count <= 0 ? 'disabled style="opacity: 0.5;"' : '' }}>
                    - <span data-text="$count > 0 ? 'Active' : 'Disabled'"></span>
                </button>

                <span data-text="$count" style="margin: 0 20px; font-size: 24px;"></span>

                <button data-on:click="@postx('/counter/increment')"
                        {{ $count >= 10 ? 'disabled style="opacity: 0.5;"' : '' }}>
                    + <span data-text="$count < 10 ? 'Active' : 'Disabled'"></span>
                </button>
            @endfragment
        </div>
    </div>
</body>
</html>

Update controller to use fragments with proper targeting:

php
public function show()
{
    $count = 0;
    return view('counter', compact('count'));
}

public function increment()
{
    $currentCount = signals('count', 0);
    $newCount = min($currentCount + 1, 10); // Cap at 10
    
    return hyper()
        ->signals(['count' => $newCount])
        ->fragment('counter', 'button-controls', ['count' => $newCount], [
            'selector' => '#counter-buttons',
            'mode' => 'inner'
        ]);
}

public function decrement()
{  
    $currentCount = signals('count', 0);
    $newCount = max($currentCount - 1, 0);
    
    return hyper()
        ->signals(['count' => $newCount])
        ->fragment('counter', 'button-controls', ['count' => $newCount], [
            'selector' => '#counter-buttons',
            'mode' => 'inner'
        ]);
}

Try it: The buttons update to show Active/Disabled state and get disabled at 0 and 10!

Understanding Fragment Options (Hyper)

  • selector - Specifies which element to target (e.g., '#counter-buttons')
  • mode - Determines how to update the element:
    • 'inner' - Updates only the inner HTML (preserves the container)
    • 'outer' - Replaces the entire element
    • 'append', 'prepend', 'before', 'after' - Other Datastar modes

Without options, fragments target by ID. With options, you can target any element and choose the update strategy.

Why Use mode: 'inner'?

Using 'inner' mode preserves the #counter-buttons div while updating its contents. This keeps any styling, attributes, or event listeners on the container element intact.

Understanding hyper()->fragment():

  • hyper()->fragment('counter', 'button-controls', $data, $options) renders the button-controls fragment from the counter.blade.php view
  • The fragment content between @fragment('name') and @endfragment gets processed with the provided data
  • Fragments are about code reuse - this same fragment could update any element on the page by using different selector options
  • The selector and mode options give you precise control over where and how the fragment updates the DOM

Key insight: Fragments keep related HTML close to where it's used while still allowing for dynamic updates. You avoid creating separate component files for small, related sections.

Step 13: Route Discovery

Writing routes manually is fine for small applications, but Hyper includes an optional route discovery system that can automatically generate routes based on your controller methods. This is inspired by Spatie's Laravel Route Discovery package but enhanced for Hyper applications.

Why Route Discovery?

As your application grows, maintaining route definitions can become tedious. Route discovery eliminates this by:

  • Automatically generating routes based on controller method names
  • Using attributes to declare HTTP methods and options
  • Following RESTful conventions
  • Keeping routing logic close to your controllers

Enable route discovery by publishing the Hyper config:

bash
php artisan vendor:publish --tag=hyper-config

Edit config/hyper.php and enable route discovery:

php
return [
    'route_discovery' => [
        'enabled' => true,
        
        'discover_controllers_in_directory' => [
            app_path('Http/Controllers'),
        ],
    ],
];

Add route attributes to your controller:

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Dancycodes\Hyper\Routing\Attributes\Route;

#[Route(middleware: 'web')]
class CounterController extends Controller
{
    public function show()
    {
        $count = 0;
        return view('counter', compact('count'));
    }

    #[Route(method: 'post')]
    public function increment()
    {
        $currentCount = signals('count', 0);
        $newCount = min($currentCount + 1, 10);
        
        return hyper()
            ->signals(['count' => $newCount])
            ->fragment('counter', 'button-controls', ['count' => $newCount], [
                'selector' => '#counter-buttons',
                'mode' => 'inner'
            ]);
    }

    #[Route(method: 'post')]
    public function decrement()
    {
        $currentCount = signals('count', 0);
        $newCount = max($currentCount - 1, 0);
        
        return hyper()
            ->signals(['count' => $newCount])
            ->fragment('counter', 'button-controls', ['count' => $newCount], [
                'selector' => '#counter-buttons',
                'mode' => 'inner'
            ]);
    }
}

Understanding Route Attributes (Hyper Feature):

  • #[Route(middleware: 'web')] on the class - All routes from this controller use the web middleware
  • #[Route(method: 'post')] on methods - Declares this method handles POST requests
  • Without the method attribute, route discovery assumes GET (for methods like show, index, etc.)
  • Route discovery automatically generates URLs based on controller and method names

Check your routes:

bash
php artisan route:list

You'll see that Hyper automatically discovered and registered these routes:

  • GET /counterCounterController@show
  • POST /counter/incrementCounterController@increment
  • POST /counter/decrementCounterController@decrement

How route discovery works:

  1. Scans your controllers for public methods
  2. Automatically generates RESTful routes based on method names
  3. Uses the #[Route] attribute to determine HTTP verbs, middleware, and other options
  4. Maps controller and method names to build URLs

Now you can remove the manual route definitions from routes/web.php. Route discovery handles everything!

Benefits of Route Discovery

  • Less Maintenance: No need to manually update routes when adding controller methods
  • Convention Over Configuration: Follows predictable patterns
  • Co-located Logic: Route attributes live next to the controller methods

Optional Feature

Route discovery is completely optional. You can continue using traditional route definitions in routes/web.php if you prefer. Both approaches work perfectly with Hyper.

What You've Learned

Congratulations! You've built a complete reactive counter application with advanced features. Here's what you now understand:

Datastar Fundamentals:

  • ✅ Signals and reactivity
  • data-signals, data-text, data-on:click
  • ✅ Signal types (regular, local)
  • ✅ Automatic UI updates

Hyper's Laravel Integration:

  • @hyper directive for setup
  • @postx() for CSRF-protected requests
  • signals() helper to read frontend state
  • hyper() helper to build responses
  • @signals directive for server initialization
  • data-error for validation display
  • hyper()->view() for rendering Blade views
  • hyper()->fragment() for code reuse
  • ✅ Fragment targeting with selector and mode options
  • ✅ Route discovery with #[Route] attributes

Development Flow:

  • ✅ Frontend sends signals with every request
  • ✅ Server processes logic and validation
  • ✅ Server returns signal updates and/or HTML
  • ✅ Frontend updates reactively

Next Steps

Now that you've built your first app, explore:

Complete Counter Code

Here's the final working code with all features:

config/hyper.php:

php
<?php

return [
    'route_discovery' => [
        'enabled' => true,
        
        'discover_controllers_in_directory' => [
            app_path('Http/Controllers'),
        ],
    ],
];

app/Http/Controllers/CounterController.php:

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Dancycodes\Hyper\Routing\Attributes\Route;

#[Route(middleware: 'web')]
class CounterController extends Controller
{
    public function show()
    {
        $count = 0;
        return view('counter', compact('count'));
    }

    #[Route(method: 'post')]
    public function increment()
    {
        $currentCount = signals('count', 0);
        $newCount = min($currentCount + 1, 10);
        
        return hyper()
            ->signals(['count' => $newCount])
            ->fragment('counter', 'button-controls', ['count' => $newCount], [
                'selector' => '#counter-buttons',
                'mode' => 'inner'
            ]);
    }

    #[Route(method: 'post')]
    public function decrement()
    {  
        $currentCount = signals('count', 0);
        $newCount = max($currentCount - 1, 0);
        
        return hyper()
            ->signals(['count' => $newCount])
            ->fragment('counter', 'button-controls', ['count' => $newCount], [
                'selector' => '#counter-buttons',
                'mode' => 'inner'
            ]);
    }
}

resources/views/counter.blade.php:

blade
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Counter App</title>
    @hyper
</head>
<body>
    <div @signals(['count' => 0, 'errors' => []])>
        <h2>Reactive Counter</h2>

        <div id="counter-buttons">
            @fragment('button-controls')
                <button data-on:click="@postx('/counter/decrement')"
                        {{ $count <= 0 ? 'disabled style="opacity: 0.5;"' : '' }}>
                    - <span data-text="$count > 0 ? 'Active' : 'Disabled'"></span>
                </button>

                <span data-text="$count" style="margin: 0 20px; font-size: 24px;"></span>

                <button data-on:click="@postx('/counter/increment')"
                        {{ $count >= 10 ? 'disabled style="opacity: 0.5;"' : '' }}>
                    + <span data-text="$count < 10 ? 'Active' : 'Disabled'"></span>
                </button>
            @endfragment
        </div>
    </div>
</body>
</html>

No routes/web.php needed! Route discovery handles everything automatically.

Alternative: Manual Routes

If you prefer not to use route discovery, you can still define routes manually in routes/web.php:

php
use App\Http\Controllers\CounterController;

Route::get('/counter', [CounterController::class, 'show']);
Route::post('/counter/increment', [CounterController::class, 'increment']);
Route::post('/counter/decrement', [CounterController::class, 'decrement']);

Then remove the #[Route] attributes from your controller. Both approaches work perfectly!

Understanding What You Built

Your counter application demonstrates all the core Hyper concepts:

  1. Reactive State - The count signal updates automatically across the UI
  2. Server Logic - Validation and business rules stay on the server
  3. CSRF Protection - Automatic via @postx()
  4. Validation - Laravel validation with automatic error display
  5. Code Reuse - Fragments avoid duplication
  6. DOM Control - Precise updates with selector and mode options
  7. Convention - Route discovery eliminates boilerplate

You now have a solid foundation in Laravel Hyper. You understand how signals flow between frontend and backend, how to validate and update state, and how to structure reactive applications.

Ready to build something amazing? Start with a real project or continue learning with more advanced topics in the documentation!