Render Livewire Components in Markdown
Combine Spatie's Markdown package with Livewire to drop interactive pieces straight into your blog posts.
Blog posts written in Markdown are easy to author, but they can feel static. Sometimes you want a small interactive piece — a counter, a demo, a comment box — embedded right in the middle of an article, without spinning up a separate page or reaching for a JavaScript framework.
In a Laravel project, you can get there with two packages you probably
already know: Spatie's laravel-markdown and Livewire. Let's walk
through how they fit together.
I.Rendering Markdown with Spatie
The spatie/laravel-markdown package ships a Blade component and a
MarkdownRenderer class to convert Markdown into HTML. It also uses
Shiki under the hood, so you get syntax highlighting for over a
hundred languages out of the box.
Install it like this:
composer require spatie/laravel-markdown
npm install shiki
Now you can render any Markdown string to HTML from anywhere in your app.
II.Creating the Livewire component
Livewire is a full-stack framework for Laravel that lets you build
dynamic interfaces without leaving Blade. As a toy example, let's
build a Counter component with a button that increments a number
and then disables itself.
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class Counter extends Component
{
public $count = 0;
public $disabled = false;
public function mount()
{
$this->count = rand(0, 10000);
}
public function increment()
{
$this->count++;
$this->disabled = true;
}
public function render(): string
{
return <<<'blade'
<div class="flex justify-center items-center gap-2">
<button
class="bg-rose-500 text-white font-extrabold h-10 w-10 rounded-full disabled:opacity-50"
wire:click="increment"
{{ $disabled ? 'disabled' : '' }}
>
+1
</button>
<p class="text-2xl font-bold">{{ $count }}</p>
</div>
blade;
}
}Nothing fancy — two public properties, a mount() to seed the
initial value, and an inline Blade view returned from render().
III.Injecting the component into Markdown
Here's the key idea: Markdown passes through Blade before it reaches
the browser. That means the @livewire directive works anywhere in
your Markdown file:
@livewire('counter')
When you render the article, Blade picks up the directive and mounts
a real Livewire component in place. To make it work, render the
stored Markdown through MarkdownRenderer and then through Blade:
<?php
use App\Models\Article;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Route;
use Spatie\LaravelMarkdown\MarkdownRenderer;
Route::get('/', function () {
$article = Article::findBySlug('create-counter-in-livewire');
$html = app(MarkdownRenderer::class)
->highlightTheme('dracula')
->toHtml($article->body);
return Blade::render(
"
@extends('welcome')
@section('content')
{$html}
@endsection
"
);
});Two passes: Markdown to HTML (with Livewire directives preserved), then Blade to render those directives into live components.
IV.Why it's nice
You keep the authoring ergonomics of Markdown, and you still get interactivity where you want it. Counters, demos, embedded forms, comment threads — anything you can build as a Livewire component, you can drop into a post with a single line. No JavaScript build step, no custom shortcode system. Just Blade doing what Blade does.