DEV Community

Cover image for Taming Laravel Blade with Fully Typed Views, Autocomplete, and Type Safety

Taming Laravel Blade with Fully Typed Views, Autocomplete, and Type Safety

Raheel Shan on September 04, 2025

Blade is messy. We all know it. Developers pass associative arrays into views and switch between multiple partials, view and controller to remember...
Collapse
 
xwero profile image
david duymelinck • Edited

I think Blade already has what the library does with components.

// views/page/home.blade.php
<x-base :content="$content"/>

// app/View/Components/Base.php
class Base extends Component
{
   public function __construct(public Content $content) {}
}

// views/components/base.blade.php
<html>
<body>
<x-header :items="$content->header"/>
<x-main :items="$content->main"/>
<x-footer :items="$content->footer">
</body>
</html>

// app/View/Components/Header.php
class Header extends Component
{
   public function __construct(public HeaderItems $items) {}
}

// and so on
Enter fullscreen mode Exit fullscreen mode
Collapse
 
raheelshan profile image
Raheel Shan • Edited

That's totally different. Components definitely help structure Blade, but they don’t give you type safety and autocomplete inside the templates themselves. With my approach, you don’t need to wire up constructors and custom classes for every piece, you get typed variables directly in your Blade files, and your IDE actually understands them. Components are useful for reusability, mine’s about developer experience and type-safety.

Collapse
 
xwero profile image
david duymelinck

Blade components give you type safety because the types of the arguments are checked.
It is not because the data isn't checked when adding it to the view, it can't be checked in the entry template, home.blade.php. That is what my example proves.

I' m sure that checking @var lines in templates is going to be more difficult and is going to require more resources than instantiating the component class.

Components don't improve the developer experience?

Sure autocomplete is a nice to have, but if you know the class it is not that hard to find out the properties you need.
To make it more convenient, the properties can be added to the template as a comment. Then you don't even need to search for the class.

Thread Thread
 
raheelshan profile image
Raheel Shan • Edited

Components check constructor types, but they don’t give inline autocomplete or prevent typos inside Blade.

It is not because the data isn't checked when adding it to the view, it can't be checked in the entry template, home.blade.php. That is what my example proves.

Once checked you are still prone to make mistakes and blade won't prevent you. That's what's solved here.

I' m sure that checking @var lines in templates is going to be more difficult and is going to require more resources than instantiating the component class.

I totally disagree here and I suggest don't be sure unless you have experienced this. A @var line is a single comment. Instantiating components requires scaffolding classes, wiring up constructors, and passing props making things diffcult. One is a hint to the IDE, the other is a whole architecture decision.

Autocomplete is just a nice-to-have.

Sure, until you’re working in a large project with 20 models and dozens of view contexts. Then autocomplete goes from nice to have to “why my sanity is still intact.” because developers need fast feedback in their editor.

I have also gone through the comments by Taylor Otwell. He says "don’t build cathedrals of complexity". My solution is exactly opposite. I add one line of type info and IDE does the heavy lifting.

Components don't improve the developer experience?

Ofcourse they don't improve the developer experience unless you need reusable UI pieces. Otherwise they add additional boilerplate.

Thread Thread
 
xwero profile image
david duymelinck

Once checked you are still prone to make mistakes

Can you give an example, I'm not sure what you mean.

A @var line is a single comment.

it is not about the @var lines in the template but the solution to check them. How are you going to check them when the data shape can be whatever? As far a I know there is no event in Blade to add a method to all partials to make this possible.
The only way I can think of doing this is by creating a custom @include directive, and in that directive read the template, extract the @var lines, and check those against the data that is added. Having a cache would improve the performance, but it still is a lot more effort than the way components work.

until you’re working in a large project with 20 models and dozens of view contexts

Why should you have a lot of models? With one model you should be able to cover most displays, and then a few other models where the data is too different from the common model to handle it. When you need too many models it could mean the project is experiencing feature creep.

I have also gone through the comments by Taylor Otwell

It seems you missed this line; Some problems are genuinely complex, but in general, if a developer finds a "clever solution" which goes beyond the standard documented way in a framework such as Laravel or Ruby on Rails, "that would be like a smell."

Your solution is not only one line of type info. Your solution requires ViewModels and adding a library. The solution is not a cathedral, but it seems like a little church to me.
That is why I'm championing components as a build-in fix for type safety and the developer experience.

they don't improve the developer experience unless you need reusable UI pieces.

I really hope you don't create specific templates for each page of a website. Because that is what that sentence is implying when i read it.

To be clear adding the @var line isn't the main problem. that is something I use often in classes to make autocomplete work.
I'm using a Jetbrains IDE and there it isn't possible to use the comment type in Blade templates. So for your solution to work, I need to change my IDE.
That is not going to happen because the value of this convenience is too small compared to all other features the IDE provides.

Thread Thread
 
raheelshan profile image
Raheel Shan

Okay, you asked for an example. If you type $conten instead of $content in home.blade.php, neither components nor Blade will complain. That’s exactly where inline type checks and autocomplete help.

I don’t need an event in Blade or a custom @include directive. A simple service provider that swaps the default ViewFactory with a typed version is enough. It’s not fancy or hacky, it just inspects the @var lines and validates the data passed in.

class TypedViewServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->extend('view', function ($factory, $app) {
            return new TypedViewFactory(
                $app['view.engine.resolver'],
                $app['view.finder'],
                $app['events']
            );
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

From there, TypedViewFactory checks the expected variables and types (including collections and arrays) against the provided data. No runtime hacks, no events, no cathedral, just a tiny extension on top of Laravel’s internals.

The ViewModel approach isn’t about multiplying models for every small case. It’s about making the data contract for a view explicit in one place. Instead of scattering expectations across includes and constructors, a ViewModel makes it clear what the template requires. That keeps things predictable and maintainable as the project grows.

On Taylor’s quote: I read it. He said “don’t build cathedrals of complexity.” That’s why I avoided wiring up constructors and components for every template. A single @var line is not a church. And just as a side note, not all applications use components; some stick to plain Blade views.

For IDE support: JetBrains not interpreting @var in Blade is an IDE limitation, not a flaw in the approach. PHPStorm and VS Code has plugins for this, and many editors support it out of the box. If your tool doesn’t, fair enough, but that doesn’t make the feature useless to others.

Finally, on developer experience: components are great for reusable UI pieces. But forcing every page-level template into a component just to feel “safe” is boilerplate, not better developer experience, My point isn’t to throw components away, but to keep them in their lane.

Collapse
 
avinashzala profile image
Avinash Zala

Very insightful! Type safety in Blade has always been tricky - this approach seems like a game changer for reducing runtime errors.

Collapse
 
raheelshan profile image
Raheel Shan • Edited

Thanks, Avinash! Type safety in Blade has always been overlooked, and I think that’s why so many runtime surprises happen. My goal was to keep Blade simple while still giving us the safety net we’re used to in modern dev. Happy to see it resonates. Here's the complete picture of how devs can achieve this.

dev.to/raheelshan/stop-treating-yo...

Collapse
 
xwero profile image
david duymelinck

As a side note: maybe this gives you food for thought developers.slashdot.org/story/25/0...

Collapse
 
raheelshan profile image
Raheel Shan • Edited

As for the components, Lets cover this part. Take a look at the example below.

<?php

namespace App\DTO;

class HeaderDTO
{
    public function __construct(
        public string $currency,
        public string $language,
        public array $menu,
    ) {}
}
Enter fullscreen mode Exit fullscreen mode

The component class

<?php

namespace App\View\Components;

use Illuminate\View\Component;
use App\DTO\HeaderDTO;

class Header extends Component
{
    public function __construct(
        private HeaderDTO $header
    ) {}

    public function render()
    {
        return view('components.header', [
            'header' => $this->header,
            // or may be 
            // 'model' => $this->header,
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

The view for component

// components/header.blade.php
@php
    /** @var \App\DTO\HeaderDTO $header */
@endphp

<div class="header">
    <div class="currency">{{ $header->currency }}</div>
    <div class="language">{{ $header->language }}</div>

    <ul>
        @foreach($header->menu as $item)
            <li>{{ $item }}</li>
        @endforeach
    </ul>
</div>
Enter fullscreen mode Exit fullscreen mode

And fnally to use

<x-header :header="$headerDto" />
Enter fullscreen mode Exit fullscreen mode

Where $headerDto is an instance of HeaderDTO passed from a controller, layout, or globally shared LayoutViewModel.

So with Laravel components, you can pass a typed DTO instead of loose arrays. This way you get strict typing, IDE autocomplete, and consistent data contracts.

In a component, I suggest always passing only one property named model, and that property should be a DTO. This keeps all your components consistent and predictable.

<x-header :model="$headerDto" />
Enter fullscreen mode Exit fullscreen mode

Let me target custom component, twig and antler next. Please wait for me to do a little more research.