Rendering FluxUI Components Inside Laravel Markdown

It’s no secret that Markdown is one of the best options for blogging, especially when combined with Laravel Folio , Sushi , and Laravel Markdown packages. But what happens if I also want to use a component library?

Markdown files will happily ignore FluxUI , which means my blog posts end up looking slightly off compared to the rest of the website. That felt odd to me, so I decided to dig a bit deeper and figure out how to make it work.

To make FluxUI components render properly, I needed to override the default Markdown renderers. In my case, I wanted to replace the default output for headings, paragraphs and links with their FluxUI equivalents.

Here’s how the custom FluxHeadingRenderer.php looks:

namespace App\Markdown;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Str;
use InvalidArgumentException;
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;

class FluxHeadingRenderer implements NodeRendererInterface
{
    public function render(Node $node, ChildNodeRendererInterface $childRenderer): HtmlElement|string|null
    {
        if (! $node instanceof Heading) {
            throw new InvalidArgumentException('Incompatible node type: ' . get_class($node));
        }

        $innerHtml = $childRenderer->renderNodes($node->children());

        $textForId = trim(strip_tags($innerHtml));
        $id = $textForId !== '' ? Str::slug($textForId) : null;

        $level = $node->getLevel();
        $sizeMap = [1 => '4xl', 2 => '3xl', 3 => 'xl', 4 => 'base']; // to match modified heading.blade.php
        $size = $sizeMap[$level] ?? 'md';

        $attributes = 'level="' . e($level) . '" size="' . e($size) . '"';
        if ($id) {
            $attributes .= ' id="' . e($id) . '"';
        }

        return Blade::render("<flux:heading $attributes> $innerHtml </flux:text>");
    }
}

Here’s how the custom FluxParagraphRenderer.php looks:

namespace App\Markdown;

use Illuminate\Support\Facades\Blade;
use InvalidArgumentException;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;

class FluxParagraphRenderer implements NodeRendererInterface
{
    public function render(Node $node, ChildNodeRendererInterface $childRenderer): HtmlElement|string|null
    {
        if (! $node instanceof Paragraph) {
            throw new InvalidArgumentException('Incompatible node type: ' . get_class($node));
        }

        $innerHtml = $childRenderer->renderNodes($node->children());

        return Blade::render("<flux:text>$innerHtml</flux:text>");
    }
}

Here’s how the custom FluxLinkRenderer.php looks:

namespace App\Markdown;

use Illuminate\Support\Facades\Blade;
use InvalidArgumentException;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;

class FluxLinkRenderer implements NodeRendererInterface
{
    public function render(Node $node, ChildNodeRendererInterface $childRenderer): HtmlElement|string|null
    {
        if (! $node instanceof Link) {
            throw new InvalidArgumentException('Incompatible node type: ' . get_class($node));
        }

        $href = e($node->getUrl());
        $text = $childRenderer->renderNodes($node->children());
        
        return Blade::render("<flux:link href='$href'>$text</flux:link>");
    }
}

Finally, both renderers need to be registered inside config/markdown.php:

use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Node\Block\Paragraph;

'block_renderers' => [
    ['class' => Heading::class, 'renderer' => App\Markdown\FluxHeadingRenderer::class, 'priority' => 10],
    ['class' => Paragraph::class, 'renderer' => App\Markdown\FluxParagraphRenderer::class, 'priority' => 10],
    ['class' => Link::class, 'renderer' => App\Markdown\FluxLinkRenderer::class, 'priority' => 10],
],

And that’s it! Laravel Markdown package makes it straightforward to hook into the rendering process and inject your own logic.

Now, every Markdown heading, paragraph and link in my blog post will render as a FluxUI component, giving a perfectly consistent look across my website.

Copyright 2025, Kirill Dakhniuk.