leafs/blade
leafs/blade provides reusable Blade components for adding interactive maps to Laravel applications. Drop a component into any view, pass coordinates and markers, and customize the map without writing JavaScript by hand. Lightweight, expressive, and easy to integrate.
leafs/blade is a thin adapter that lets Leaf PHP render views with the Blade compiler from illuminate/view (it leans on jenssegers/blade for the standalone binding). Treat it as "Blade without Laravel": syntax is identical, the surrounding helpers are not.
v5.0 is a platform bump, not a cosmetic release. Before you install, check two things: PHP 8.2+, and illuminate/view ^11 | ^12 | ^13. If your app or another dependency is still on Laravel 9/10 components, v5 will not resolve — see Gotchas before you try.
Where to look first, in this order:
README.md — the only place the supported constructor signature, Leaf integration snippet, and the new @lingo / @tl directive setup are documented.src/Blade.php — it is small; read it end-to-end to see exactly which jenssegers/blade methods are forwarded, which are shadowed, and where the Crash breadcrumb hooks are wired in.composer.json — the illuminate/view and php constraints are the single biggest integration risk in an existing project, and both changed in v5.0.app/config / config/view.php depending on version) — that is where views and cache paths are wired.leafphp.dev/docs/routing/error-handling — the Leaf Crash docs, now that Blade feeds view breadcrumbs into error reports.Minimal steps:
php -v # must be 8.2+
composer require leafs/blade
composer why illuminate/view # must resolve to ^11 | ^12 | ^13
mkdir -p views storage/cache/views
// views/home.blade.php
<h1>Hello {{ $name }}</h1>
// index.php
require __DIR__ . '/vendor/autoload.php';
use Leaf\Blade;
$blade = new Blade(__DIR__ . '/views', __DIR__ . '/storage/cache/views');
echo $blade->render('home', ['name' => 'Leaf']);
First use case to build: a single authenticated dashboard page with one layout and two partials, plus one string passed through @lingo. That exercises the things you actually need to validate — layout inheritance, cache directory writability, data passing, and whether Lingo is correctly wired for the new directives — before you commit to migrating a whole app's templates. Then throw a deliberate exception inside a partial and confirm the view appears in the Leaf Crash report with a duration; that is the headline v5.0 feature and you want to know it works before you are debugging an outage.
If you are on Leaf 3, the idiomatic wiring is to attach the engine to Leaf's view layer once at bootstrap (View::attach(\Leaf\Blade::class, 'blade') or the equivalent in your Leaf version) and then call View::render('home', $data) from routes. Confirm the exact call against the README for your installed tag; the attach API has moved between Leaf majors.
Engine registration at bootstrap. Attach Blade once, in index.php or a service provider, before any route runs. Do not instantiate a new Blade per request unless you deliberately want a cold compiler — the compiler holds resolved view state and re-instantiating throws that away. This matters more in v5: a single shared instance is also what gives Leaf Crash a coherent chain of breadcrumbs for the request.
Layout + partials, the standard shape.
{{-- views/layouts/app.blade.php --}}
<html><body>
@include('partials.nav')
@yield('content')
</body></html>
{{-- views/dashboard.blade.php --}}
@extends('layouts.app')
@section('content')
<p>Signed in as {{ $user['name'] }}</p>
@endsection
Translations with @lingo / @tl. v5.0 adds first-class directives for Leaf Lingo. Keep translation keys in templates and all copy in Lingo's store — do not hardcode English strings "for now", because the moment you have two locales you will be grepping templates instead of language files.
{{-- verify exact argument shape against the README for your tag --}}
<h1>@lingo('dashboard.title')</h1>
<p>@tl('dashboard.greeting', ['name' => $user['name']])</p>
If Lingo is not installed and configured, these directives have nothing to compile to — establish the Lingo bootstrap before the Blade engine so the compiled PHP can resolve its translator at render time. See Gotchas.
Let Leaf Crash do the work. The breadcrumb trail (which views rendered, in what order, and how long each took) is emitted automatically on every render. You do not need to instrument your templates. The pattern that pays off is making sure your error handler actually surfaces the report in non-production environments, then reading the trail to find the slow or repeated view, not just the failing one — a partial rendered 40 times in a loop is usually the real cost centre.
Shared data instead of repeating yourself. Anything needed by every view (current user, app name, flash messages) belongs in one place. If the wrapper exposes share()/shareData(), use it after construction; if it does not, write a small ViewData class that merges base data into the array you pass to render(). That second option is more portable and survives a swap of the package.
$blade->share('appName', 'Acme'); // verify availability in src/Blade.php
echo $blade->render('dashboard', ['user' => $user]);
Custom directives as your escape hatch. The moment you need loop counters, money formatting, or environment badges, register a directive rather than dropping to {!! !!} with hand-built HTML:
$blade->directive('money', fn ($expr) => "<?php echo number_format($expr, 2); ?>");
Middleware-driven view data. In Leaf, push shared view data from middleware (auth, locale, flash) so controllers stay thin and every template sees a consistent set of variables. Locale-in-middleware pairs naturally with @lingo: set the locale once per request, and every @lingo string in every view follows.
Testing. Point Blade at tests/fixtures/views and tests/fixtures/cache and assert on rendered strings. Keep the fixture cache directory out of git and wipe it in setUp() so a stale compiled file never masks a failing test. v5.0 moves the upstream suite to Alchemy, which is an internal detail — it is a maintenance signal, not something you need to adopt. Add one test that asserts a @lingo key renders translated text; that is the regression most likely to slip through.
Deployment rhythm. Compile-on-first-request is fine in dev; in production warm the cache during deploy (render each top-level view once in a script) and ship the compiled directory alongside the code, or accept one slow request per view per release. On a major bump like 5.0, wipe the cache directory before warming — compiled files from v4 are not something you want served.
illuminate/view ^11 | ^12 | ^13 plus PHP 8.2+ is a hard floor. Any app still pulling illuminate/* at 9 or 10 cannot install this version, and any host on PHP 8.0/8.1 cannot run it. Run composer why illuminate/view before and after installing, and check php -v in CI as well as locally — the CI image is where this bites.@csrf, @auth, @guest, @can, route(), asset(), old(), csrf_field() will all fatal with "undefined function" the first time a template renders. Either define plain PHP functions with those names in a bootstrap file, or (better) ban the directives in your templates and pass raw values in. Note that @lingo and @tl are not in this list only because this package registers them — every other Laravel directive still is.@lingo / @tl without Lingo is a blank-page class of failure. Confirm Leaf Lingo is required, bootstrapped before the view engine, and has a translator available at render time. If the directive compiles to a call that cannot resolve, you get the same silent white output as any other compile/render failure. Test one translated string in isolation before rolling translations across templates.__DIR__ or base_path(); never from bare 'views'.view:clear command here.dashboard.blade.php to a hashed filename, but the compiled PHP is readable and shows exactly what a directive expanded to — including what @lingo became — far faster than guessing at @ syntax. Pair this with the Leaf Crash breadcrumb trail: the trail tells you which view, the compiled file tells you why.{{ }} escapes, {!! !!} does not. With no framework-level sanitisation layer, every {!! !!} is a deliberate XSS decision. Audit them; you will find more than you expect after a Laravel migration, because Laravel apps often rely on helpers that sanitise downstream.display_errors off, a missing @endsection or an unclosed @if yields white output. Check error_log first, then read the Leaf Crash report — if the breadcrumb trail stops before the view you expected, the failure is in compilation, not in your data.home to home.blade.php or home.php in your version; a mismatch shows up as "view not found" even though the file plainly exists. The compiler's exists() check is the fastest way to prove the path is wrong rather than the name."leafs/blade": "5.0.*"), and isolate usage behind your own View facade or a one-file Template class so replacing the engine is a contained change rather than a grep across the codebase.if statements, shared data, the Compiler instance itself if you need to register component-style tags, and now @lingo / @tl. Everything else is upstream Blade behaviour and should be looked up in the illuminate/view docs, not here.How can I help you explore Laravel packages today?