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:
<?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:
<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:
<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:
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:
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:
<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:
<?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:
// 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:
<!-- 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:
<!-- Start a background stream -->
<button @click="livue.stream('ask', ['What is Vue?'], { background: true })">
Ask AI
</button>
// Listen for the stream-complete event
livue.$el.addEventListener('livue:stream-complete', function(e) {
// e.detail.response contains the full server response
// Apply it when ready (e.g. when the user navigates back)
livue.applyStreamResponse(e.detail.response);
});
This is particularly useful for multi-session UIs (e.g. chat applications) where the user might navigate away while a stream is in progress. You can store the response and apply it later: