illuminate/view
Standalone view rendering and Blade templating component from Laravel. Compile and cache Blade templates, compose nested views, share data across views, and use view composers and creators to build reusable, maintainable PHP templates for Laravel or any PHP project.
In a Laravel application, illuminate/view is already wired up — you get it transitively via laravel/framework, ViewFactory is bound to the container, Blade is registered as the compiler, and the view() helper is aliased. There is nothing to install. Confirm it's present with composer show illuminate/view; only add composer require illuminate/view explicitly if you're building a standalone package/console app that renders Blade without a full Laravel kernel.
Where to look first
config/view.php — paths (where templates live) and compiled (where the compiled PHP cache is written).resources/views/ — your template tree; the default FileViewFinder root.Illuminate\View\ViewFactory → Illuminate\View\View → Illuminate\View\Compilers\BladeCompiler — this is the object graph that actually does the work. When something behaves oddly, the trace is almost always make() → find() → compile() → render().Illuminate\View\Concerns\ManagesLayouts and ManagesComponents if you need to understand how @extends/@section and <x-…> resolve.First use case — a layout and a page, which is the shape 90% of your views will take:
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head><title>@yield('title', config('app.name'))</title></head>
<body>
@include('partials.nav')
@yield('content')
</body>
</html>
{{-- resources/views/dashboard/index.blade.php --}}
@extends('layouts.app')
@section('title', 'Dashboard')
@section('content')
<h1>Hello, {{ $user->name }}</h1>
@foreach ($stats as $stat)
<x-stat-card :stat="$stat" />
@endforeach
@endsection
Route::get('/dashboard', fn () => view('dashboard.index', [
'user' => auth()->user(),
'stats' => Stats::all(),
]));
Then php artisan view:cache before shipping and you're operating the package the way it's designed to be used.
Layouts vs. components — pick per surface, don't mix. Use @extends/@section/@yield for page-level shells (one per app, maybe one per section). Use class-based or anonymous Blade components (<x-alert>, @props) for reusable UI atoms. Mixing both in one template is the most common source of "why is my section empty" bugs.
View composers for data that isn't request-specific. Anything a view needs but the controller has no business knowing (nav counts, feature flags, current team) belongs in a composer, not a View::share pile in AppServiceProvider:
View::composer(['partials.nav', 'layouts.app'], function ($view) {
$view->with('navTeams', once(fn () => auth()->user()?->teams ?? collect()));
});
View::composer('*', …) runs for every render including partials and mail — use it sparingly and memoize with once().
Shared data via View::share for genuinely global values (site name, a ViewErrorBag-adjacent object). Note share() is evaluated once per request, so don't put request-dependent state there outside a middleware.
Package views use namespace hints, not __DIR__ concatenation.
$this->loadViewsFrom(__DIR__.'/../resources/views', 'billing');
// usage: view('billing::invoice.show')
// override: resources/views/vendor/billing/invoice/show.blade.php
View::addLocation() is the equivalent for app-level modular views; the finder checks namespaced hints first, then locations in registration order.
Stacks for per-page assets. @push('scripts') / @stack('scripts') keeps page-specific JS out of the layout. Pair with @once so a component included five times only emits its <script> once. This is the idiomatic replacement for "dump everything in the layout."
Returning partials for AJAX/htmx. view('rows.item', …)->render() (or a JsonResponse containing it) is the clean way to serve HTML fragments. Prefer dedicated partials over re-rendering the full page and parsing it client-side.
String rendering without a file. Blade::render('Hi {{ $name }}', ['name' => $n]) is extremely useful for mail subjects, notifications, log messages, and user-configurable templates — it uses the same compiler, so custom directives work.
Directive extension points. Register app-specific directives in a service provider's boot():
Blade::directive('money', fn ($expr) => "<?php echo number_format((float) {$expr}, 2); ?>");
Blade::if('admin', fn () => auth()->user()?->isAdmin() ?? false);
Prefer Blade::if() over a raw string directive when you're producing control flow — it composes with @elseadmin and stays readable. For anything with a template body, write a component instead; directives can't accept slots.
Performance posture. @include inside a loop re-runs the finder and compiler checks each iteration; hoist the partial or use @each. On very hot pages, prefer a component with a memoized view-model or eager-loaded data over per-row queries inside the template. php artisan view:cache in deploy removes the compile step entirely.
php artisan view:clear. In Docker/K8s, config('view.compiled') (storage/framework/views) must be writable by the PHP user, or you'll get opaque file_put_contents(): failed to open stream errors.view() only exists in a booted Laravel app. In a bare illuminate/view setup you must construct ViewFactory yourself with a FileViewFinder and FileCompilerEngine, and register the @section/@component helpers via BladeCompiler::directive() — the view() helper, @csrf, @auth, @vite, and @error all depend on container services that aren't there.{!! !!} is a real XSS hole. Blade's default {{ }} uses e(). Any raw echo of user data — including inside attributes and JSON blobs — needs Js::from($data) (for script context) rather than string interpolation.@error/$errors requires the ViewErrorBag to have been shared. It arrives via ShareErrorsFromSession middleware; if you render views outside the web group (console, queue, custom route group), $errors is undefined and templates throw. Guard with @if($errors->any()) only after confirming the middleware, or share an empty ViewErrorBag yourself.@vite/@csrf/@method silently degrade if their services aren't bound. In standalone usage, either provide the Vite/session bindings or replace those directives with stubs. Symptom: the directive renders literally or throws a "Target class does not exist" from the container.'*' composer runs after specific ones, so it can clobber data. Name your target views instead of relying on '*'.Blade::directive() call, existing compiled views keep the old output until view:clear. Same for Blade::if().@props vs $attributes. In anonymous components, undeclared attributes fall into $attributes and must be echoed with {{ $attributes }} to render — forgetting this is why your class="…" passed to <x-button> vanishes.$loop is only available inside @foreach, and nested loops shadow the parent. Use $loop->parent->index to reach outward. @forelse also provides it.View::exists() is cheap, View::make() is not. Validate a user-supplied template name with exists() before rendering; passing arbitrary strings to view() combined with addLocation() is a path-traversal primitive.app('view')->getFinder()->find('dashboard.index') shows which file actually resolved; Blade::render('{{ 1+1 }}') confirms the compiler is live; app('blade.compiler')->getExpressionTags() reveals the configured {{ }} delimiters if your JS framework clashes with them (change via Blade::setEchoFormat() or compiled options).php artisan view:cache (builds all templates up front — do it after the release symlink is in place), and if you use OPcache, ensure opcache.validate_timestamps behaviour doesn't re-stat the compiled directory thousands of times per request in dev.Illuminate\View\Compilers\BladeCompiler to override compilation behaviour, then resolve('blade.compiler') in a provider or bind your subclass before ViewServiceProvider boots. For most needs, Blade::directive() is preferable — subclassing forfeits that API for anyone else in the app.How can I help you explore Laravel packages today?