Technical · Dispatch № 21

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.