Livewire UI Components - v2.0.0
StreamableContent Component
The StreamableContent component provides a container for displaying streaming content, such as AI-generated text responses. It leverages Livewire 4's wire:stream directive to display content in real-time as it's generated.
Note: This component was added in version 2.0.0. Streaming functionality requires Livewire 4 or higher. On Livewire 3, content is displayed statically.
Basic Usage
<?php
use Livewire\Volt\Component;
new class extends Component {
public string $aiResponse = '';
public function generateResponse(): void
{
// Stream content to the component
$this->stream('ai-response', 'Hello, ');
$this->stream('ai-response', 'how can I help you today?');
}
}; ?>
<x-artisanpack-streamable-content target="ai-response" />
<x-artisanpack-button wire:click="generateResponse">
Generate Response
</x-artisanpack-button>
Examples
Streaming AI Responses
<?php
use Livewire\Volt\Component;
use OpenAI\Laravel\Facades\OpenAI;
new class extends Component {
public string $prompt = '';
public bool $isStreaming = false;
public function chat(): void
{
$this->isStreaming = true;
$stream = OpenAI::chat()->createStreamed([
'model' => 'gpt-4',
'messages' => [
['role' => 'user', 'content' => $this->prompt],
],
]);
foreach ($stream as $response) {
$this->stream(
'chat-response',
$response->choices[0]->delta->content ?? ''
);
}
$this->isStreaming = false;
}
}; ?>
<div class="space-y-4">
<x-artisanpack-input
wire:model="prompt"
label="Your message"
placeholder="Ask me anything..." />
<x-artisanpack-button wire:click="chat" wire:loading.attr="disabled">
<span wire:loading.remove>Send</span>
<span wire:loading>Generating...</span>
</x-artisanpack-button>
<x-artisanpack-streamable-content
target="chat-response"
prose
placeholder="AI response will appear here..."
show-cursor />
</div>
With Prose Styling
Enable typography styling for rich text content:
<x-artisanpack-streamable-content
target="markdown-response"
prose />
This applies Tailwind's prose classes for proper formatting of headings, lists, code blocks, and other markdown-generated content.
With Blinking Cursor
Show a blinking cursor animation during streaming:
<x-artisanpack-streamable-content
target="ai-response"
show-cursor />
The cursor automatically hides when streaming is complete by adding the data-streaming-complete attribute to the element.
With Placeholder Text
Display placeholder text before content starts streaming:
<x-artisanpack-streamable-content
target="response"
placeholder="Waiting for response..." />
Custom HTML Tag
Use a different HTML element instead of div:
{{-- Use a paragraph element --}}
<x-artisanpack-streamable-content
target="response"
tag="p"
class="text-lg" />
{{-- Use a section element --}}
<x-artisanpack-streamable-content
target="response"
tag="section"
class="bg-base-200 p-4 rounded-lg" />
{{-- Use a span for inline content --}}
<x-artisanpack-streamable-content
target="inline-response"
tag="span" />
Chat Interface Example
<?php
use Livewire\Volt\Component;
new class extends Component {
public array $messages = [];
public string $input = '';
public bool $isStreaming = false;
public function sendMessage(): void
{
if (empty($this->input)) return;
// Add user message
$this->messages[] = [
'role' => 'user',
'content' => $this->input,
];
$userInput = $this->input;
$this->input = '';
$this->isStreaming = true;
// Simulate AI response streaming
$response = "I received your message: \"{$userInput}\". ";
$response .= "This is a simulated streaming response. ";
$response .= "In a real application, you would integrate with an AI API.";
foreach (str_split($response) as $char) {
$this->stream('ai-response', $char);
usleep(20000); // 20ms delay for effect
}
// Add AI response to messages
$this->messages[] = [
'role' => 'assistant',
'content' => $response,
];
$this->isStreaming = false;
}
}; ?>
<div class="flex flex-col h-96">
{{-- Message History --}}
<div class="flex-1 overflow-y-auto space-y-4 p-4">
@foreach($messages as $message)
<div class="{{ $message['role'] === 'user' ? 'text-right' : 'text-left' }}">
<div class="inline-block p-3 rounded-lg {{ $message['role'] === 'user' ? 'bg-primary text-primary-content' : 'bg-base-200' }}">
{{ $message['content'] }}
</div>
</div>
@endforeach
{{-- Streaming Response --}}
@if($isStreaming)
<div class="text-left">
<div class="inline-block p-3 rounded-lg bg-base-200">
<x-artisanpack-streamable-content
target="ai-response"
show-cursor />
</div>
</div>
@endif
</div>
{{-- Input Area --}}
<div class="border-t p-4">
<form wire:submit="sendMessage" class="flex gap-2">
<x-artisanpack-input
wire:model="input"
placeholder="Type your message..."
class="flex-1" />
<x-artisanpack-button type="submit" :disabled="$isStreaming">
Send
</x-artisanpack-button>
</form>
</div>
</div>
With Initial Content
Provide initial content using the default slot:
<x-artisanpack-streamable-content target="response">
<p>This is the initial content that will be replaced when streaming begins.</p>
</x-artisanpack-streamable-content>
Marking Streaming Complete
To hide the cursor when streaming is complete, add the data-streaming-complete attribute via JavaScript:
<x-artisanpack-streamable-content
id="my-stream"
target="response"
show-cursor />
<script>
// When streaming is complete
document.getElementById('my-stream').setAttribute('data-streaming-complete', 'true');
</script>
Or dispatch a Livewire event to handle it:
// In your Livewire component
$this->dispatch('streaming-complete');
// In your Blade template
<div
x-data
@streaming-complete.window="$el.querySelector('[data-streamable-content]').setAttribute('data-streaming-complete', 'true')">
<x-artisanpack-streamable-content target="response" show-cursor />
</div>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
target |
string | required | The wire:stream target identifier |
id |
string|null | null |
Optional custom ID for the element |
tag |
string | 'div' |
The HTML tag to use for the container |
prose |
bool | false |
Whether to apply prose styling for rich text |
placeholder |
string|null | null |
Placeholder text shown before content |
show-cursor |
bool | false |
Whether to show a blinking cursor during streaming |
Livewire Streaming
To stream content from your Livewire component, use the stream() method:
// Stream text to the target
$this->stream('target-name', 'Some text content');
// Stream multiple times to append content
$this->stream('response', 'Hello ');
$this->stream('response', 'World!');
// Result: "Hello World!"
Important: The stream() method is only available in Livewire 4+. In Livewire 3, you should use traditional property updates or polling instead.
Livewire 3 Fallback
When using Livewire 3, the component renders without the wire:stream directive. You can still use it as a container for content that updates via traditional Livewire property binding:
<?php
// Livewire 3 approach
use Livewire\Volt\Component;
new class extends Component {
public string $response = '';
public function generateResponse(): void
{
// Build response (no streaming in LW3)
$this->response = 'This is the complete response.';
}
}; ?>
{{-- In Livewire 3, just use a regular element --}}
<div>{{ $response }}</div>
{{-- Or use the component without streaming --}}
<x-artisanpack-streamable-content target="unused">
{{ $response }}
</x-artisanpack-streamable-content>
CSS Customization
The blinking cursor animation can be customized by overriding the CSS:
/* Custom cursor character */
.streaming-cursor::after {
content: '|'; /* Change from block to pipe */
}
/* Custom animation speed */
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
Notes
- Streaming functionality (
wire:stream) requires Livewire 4 or higher - The component gracefully degrades in Livewire 3 by rendering content statically
- The
proseprop applies Tailwind Typography plugin classes for rich text - When using
show-cursor, adddata-streaming-completeattribute to hide the cursor when done - Content is appended to the element, not replaced, matching
wire:streambehavior