v1 LiVue v1 is here — server-driven reactivity for Laravel using Vue.js Get Started →

Features

Built-in features that handle common tasks: file uploads and downloads, real-time streaming, SPA navigation, confirm dialogs, loading indicators, tab synchronization, benchmarking, pagination and reusable PHP composables.

File Uploads

LiVue provides a complete file upload system with temporary storage, progress tracking, image previews and validation. Files are uploaded via a dedicated endpoint, stored temporarily on the server, and then your component decides where to persist them.

Enabling Uploads

Add the WithFileUploads trait to your component. Properties are detected automatically by their type hint:

app/LiVue/PhotoUploader.php
<?php

namespace App\LiVue;

use LiVue\Component;
use LiVue\Features\SupportFileUploads\WithFileUploads;
use LiVue\Features\SupportFileUploads\TemporaryUploadedFile;

class PhotoUploader extends Component
{
    use WithFileUploads;

    public ?TemporaryUploadedFile $photo = null;

    public array $documents = [];

    public function fileRules(): array
    {
        return [
            'photo' => ['image', 'max:2048'],       // 2MB, images only
            'documents' => ['file', 'max:5120'],     // 5MB, any file
        ];
    }

    public function savePhoto(): void
    {
        // Store with auto-generated name
        $path = $this->photo->store('photos');

        // Or store with a specific name
        // $path = $this->photo->storeAs('photos', 'avatar.jpg');

        $this->photo = null;
    }
}

Template

Use livue.upload() to handle the file input. After upload, the property contains metadata for display including a temporary preview URL for images:

resources/views/livue/photo-uploader.blade.php
<div>
    <!-- File input -->
    <input type="file" accept="image/*"
           @change="livue.upload('photo', $event.target.files[0])">

    <!-- Progress bar -->
    <div v-if="livue.uploading">
        Uploading... {{ livue.uploadProgress }}%
        <div :style="{ width: livue.uploadProgress + '%' }"></div>
    </div>

    <!-- Preview after upload -->
    <div v-if="photo">
        <img v-if="photo.previewUrl" :src="photo.previewUrl">
        <span v-text="photo.originalName"></span>
        <button @click="livue.removeUpload('photo')">Remove</button>
    </div>

    <!-- Errors -->
    <span v-if="$errors.photo" v-text="$errors.photo"></span>

    <button @click="savePhoto()">Save</button>
</div>

Multiple File Uploads

For array properties, use livue.uploadMultiple(). All files are sent in a single HTTP request. Valid files are accepted even if others fail validation:

Template
<input type="file" multiple
       @change="livue.uploadMultiple('documents', $event.target.files)">

<div v-for="(doc, index) in documents" :key="index">
    {{ doc.originalName }} - {{ Math.round(doc.size / 1024) }} KB
    <button @click="livue.removeUpload('documents', index)">Remove</button>
</div>

TemporaryUploadedFile Methods

After a successful upload, the PHP property holds a TemporaryUploadedFile with these methods:

Method Description
store($dir, $disk) Store with a random filename. Returns the path.
storeAs($dir, $name, $disk) Store with a specific filename. Returns the path.
temporaryUrl() Returns a signed URL for image preview (expires in 30 min).
getOriginalName() Returns the original file name.
getMimeType() Returns the MIME type.
getSize() Returns the file size in bytes.
delete() Deletes the temporary file.

Tip — Validation rules defined in fileRules() are applied at upload time, before the file is accepted. This means invalid files are rejected immediately, not when you call validate().

File Downloads

Trigger file downloads from server-side actions. LiVue provides two methods: one for existing files on disk and another for dynamically generated content.

download()

Download a file from the filesystem or a storage disk:

Component
public function downloadReport(): void
{
    // From local filesystem
    $this->download(storage_path('reports/monthly.pdf'), 'report.pdf');
}

public function downloadInvoice(int $id): void
{
    $invoice = Invoice::findOrFail($id);

    // From a storage disk (e.g. S3)
    $this->download(
        path: $invoice->file_path,
        name: "invoice-{$invoice->number}.pdf",
        disk: 's3'
    );
}

downloadContent()

Generate content on the fly and trigger a download. This is useful for exporting data as CSV, JSON or any other format:

CsvExporter.php
public function exportCsv(): void
{
    $users = User::all();

    $csv = "Name,Email\n";
    foreach ($users as $user) {
        $csv .= "{$user->name},{$user->email}\n";
    }

    $this->downloadContent($csv, 'users.csv', [
        'Content-Type' => 'text/csv',
    ]);
}

Template

Downloads are triggered from the template like any other action. The file is downloaded in the background without blocking the UI:

Template
<button @click="exportCsv()" :disabled="livue.loading">
    <span v-if="livue.isLoading('exportCsv')">Generating...</span>
    <span v-else>Export CSV</span>
</button>

Tip — Under the hood, the server creates an encrypted token containing the file path or content. The JavaScript runtime then fetches the file via a separate GET /livue/download endpoint. The token expires after 5 minutes.

Streaming

Stream content from the server to the client in real-time during long-running operations. This uses HTTP streaming (not WebSockets) via NDJSON, making it ideal for AI text generation, live logs, progress updates and more.

Enabling Streaming

Add the WithStreaming trait and use the stream() method to send data chunks to the client:

app/LiVue/AiChat.php
<?php

namespace App\LiVue;

use LiVue\Component;
use LiVue\Features\SupportStreaming\WithStreaming;

class AiChat extends Component
{
    use WithStreaming;

    public string $response = '';

    public function ask(string $question): void
    {
        $stream = $this->openai->chat($question, stream: true);

        foreach ($stream as $chunk) {
            $this->stream(to: 'output', content: $chunk);
        }

        $this->response = $stream->getFullResponse();
    }
}

The stream() Method

Each call sends a chunk to a named target in the template. By default, content is appended. Use replace: true to overwrite the previous content instead:

Component
// Append mode (default) - content accumulates
$this->stream(to: 'output', content: 'Hello ');
$this->stream(to: 'output', content: 'World!');
// Result: "Hello World!"

// Replace mode - content is overwritten
$this->stream(to: 'progress', content: '50%', replace: true);
$this->stream(to: 'progress', content: '100%', replace: true);
// Result: "100%"

v-stream Directive

Mark elements as stream targets with the v-stream directive. Use livue.stream() instead of a regular method call to invoke the streaming method:

Template
<!-- Append mode (default) -->
<div v-stream="'output'">Waiting for response...</div>

<!-- Replace mode -->
<div v-stream.replace="'status'">Ready</div>

<!-- Blinking cursor while streaming -->
<span v-if="livue.streaming" class="animate-pulse">|</span>

<button
    @click="livue.stream('ask', ['What is Vue?'])"
    :disabled="livue.streaming"
>
    <span v-if="livue.streaming">Generating...</span>
    <span v-else>Ask AI</span>
</button>

Tip — Streaming requests are automatically isolated from the request pooling system. The component state is updated normally when the stream finishes. You can also stream to multiple targets simultaneously.

Background Streaming

By default, livue.stream() automatically applies the final server response when the stream ends. With background: true, the response is not applied automatically — instead, a livue:stream-complete event is fired, giving you full control over when to apply the update:

Template
<!-- Start a background stream -->
<button @click="livue.stream('ask', ['What is Vue?'], { background: true })">
    Ask AI
</button>