
Blade is messy. We all know it. Developers pass associative arrays into views and switch between multiple partials, view and controller to remember...
For further actions, you may consider blocking this person and/or reporting abuse
I think Blade already has what the library does with components.
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.
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.
Components check constructor types, but they don’t give inline autocomplete or prevent typos inside Blade.
Once checked you are still prone to make mistakes and blade won't prevent you. That's what's solved here.
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.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.
Ofcourse they don't improve the developer experience unless you need reusable UI pieces. Otherwise they add additional boilerplate.
Can you give an example, I'm not sure what you mean.
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.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.
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.
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.
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.
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
andVS 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.
I agree there will be no errors in the IDE, or only a vocabulary error. But that is why there are tests suites, because at runtime it will error. Never trust your manual testing.
That is a major change to the framework for me. This implies it should also work when the project is using another template engine?
The service provider is simple, but I think the consequences are not.
How does the class know the expected variables and types? By reading the templates and extracting the
@var
lines, right?And how does it know the provided data from the ViewModel for a specific template?
I never mentioned that, so I don't know why that is relevant.
You mention developer experience a lot because of the
@var
lines, but if not all editors support it is that a good developer experience? That was the point I was trying to get across.I'm not the one that is promoting type safety in templates, you are. I'm not forcing anyone, I'm providing a build-in alternative.
And it is perfectly normal to use a page level component. The only thing I added is an attribute to pass the data.
Tests are great, but they catch issues after execution, while template type safety prevents them before runtime, both serve different stages.
Swapping the ViewFactory isn’t a major rewrite; it’s exactly what Laravel’s container is meant for and only touches Blade unless extended further. The factory simply reads @var lines and compares them with the $data array, no hidden tricks.
Editor support varies, but where it exists the benefit is immediate, and teams can opt in without overhead.
Components remain valuable for reusable UI, but ViewModels give page templates a clear contract without forcing everything into component boilerplate.
I don't see how this is simple? You do need to know how deep the templates go and there are multiple ways of adding sub-templates.
I don't think this example is too far fetched.
You also ignored my other engines comment. Adding
@var
lines can be different in another template engine.I worked with Laravel people who preferred Twig over Blade. And Statamic uses the Antlers template engine.
If you think the component class doesn't fit your style, there are also anonymus components. The problem there is that you can define the variables, you can't force the type.
While I agree that type safety is important, and developer experience is a nice goal. I feel creating your own pattern made you blind for alternatives.
If you make a pros and cons list of your way of working, how many cons do you have?
You said the factory reading @var lines isn’t simple because of sub-templates. For includes, that’s already handled — each partial can have its own typed property in the ViewModel, no matter how deep it goes. For other mechanisms like @extends or @yield, the contract is still checked at the entry template, so the depth doesn’t blow things up.
Take a look at the below example. For layout pieces like header and footer, I treat them as global ViewModel properties so they’re available in every view automatically, not something wired up per controller. That way they’re still typed, and autocomplete works inside their own partials.
And in layouts/app.blade.php:
This way, header and footer are globally shared, still type-checked, and their partials get autocomplete like any other view.
On components: in the end, they still render Blade, so adding a @var type definition on top of the component view works the same way. I haven’t dug into applying this directly on component classes yet, but nothing stops that either.
Other engines: I skipped that deliberately. This is Blade-specific, not Twig or Antlers. Different ecosystem, different rules.
Anonymous components: they’re lightweight, yes, but they don’t and won't let you enforce type contracts. You can pass variables, but you can’t guarantee their shape without something like my approach.
Pros and cons: sure, my approach has cons — IDE dependency, focusing only on Blade, adding a small layer. But the pros (inline type checks, autocomplete, explicit contracts, less boilerplate) matter more in practice. It’s not blindness to alternatives, it’s choosing the trade-offs that improve day-to-day developer experience.
Isn't that breaking your own premise of only having one typed data object for the view.
Your example is a fancy way to do
view('page', ['model' => $model, 'header' => $model->header, 'footer' => $model->footer])
Statamic is a Laravel based CMS. And as I mentioned before Twig is used in Laravel by developers. So it is not a different ecosystem.
My question was not about the cons themselves, but about your expectations of the way of working.
I'm glad you have a rational way of thinking about the solution.
Header/footer being global doesn’t break the premise — they’re still part of a typed LayoutViewModel, just exposed differently, and still benefit from strict typing and autocomplete.
As for Twig/Antlers, I deliberately scoped this to Blade since that’s the ecosystem I’m targeting.
And yes, every approach has cons, but my expectation here is enforcing discipline: one typed object per view context. It’s less about ignoring alternatives and more about keeping things consistent.
The idea, as far I understand it, is that the template checks the data with the
@var
lines. This means if you add data to the view for partials, you should add@var
lines for that data too. Otherwise you are not sure that not all data is type checked.What is the benefit of having the header data in the ViewModel if it is never going to used in the templates as
$model->header
. It is just ending duplicate data to the same endpoint. If it was a request that would be the first thing you would fix.My example only shows two levels of inclusions, but I have seen frontends with five levels of inclusions because the frontend developers where using the atomic design principle.
I don't see how your way of working can handle type safety in that situation.
You’re right that every partial should declare its own
@var
lines if it expects typed data. That’s exactly the point of the approach — each piece of the view tree has a contract.As for “duplicate data,” I see it differently. The LayoutViewModel carries the header/footer data because layouts are part of the page contract too. The header template doesn’t consume $model->header directly; it consumes its own HeaderDTO. That’s not duplication, that’s separation of concerns.
On deep nesting: even if you go five levels down (atomic design style), it’s still just nested ViewModels/DTOs. Each level passes its child type down explicitly. Here’s a simple 3-level example.
So whether it’s two levels or five, you always know exactly what type flows into each view. Autocomplete will still follow the chain (
$page->header->menu[0]->title
) and surfaces the correct properties at every depth, keeping type safety intact across the whole tree.Please read my comment again about the
@var
lines. To make it more clearHow else are you sure the template and sub templates get the right data?
If you only add the model check it still could go wrong.
A scenario we haven't addressed yet is conditional content. How are you going to check that?
The rigidity of a single model is already showing cracks with simple examples. Which means the more complex the templates become the more you need to fight it. And that is not a good developer experience.
Okay, I think you’re missing my point. What I recommend is a single @var line per Blade file. So instead of this.
Do this.
That’s how templates and sub-templates get the right data.
It only goes wrong if the ViewModel itself isn’t structured correctly. With a PageViewModel that already defines
$header
and$footer
, a single@var
PageViewModel$model
covers everything. If the contract is wrong, the type check fails at the source, not scattered across annotations. If contract is fine but some sub-template has wrong type, it will throw the error.For conditional content, you have two clean options.
If your ViewModel is set up properly and you stick to the pattern, you won’t run into these issues. Problems arise only when the rules are bent — but that’s true for any design practice.
And for context: this pattern is already well-established in the .NET ecosystem. Laravel developers may not be used to it yet, but with consistent use, it can become second nature and even a standard.
If you add globals, see your share method example, you suggest to still use one
@var
line?In the new example you are not using the share method, which makes it a problem to check the type because the sub template doesn't know the data without needing to read the include code.
Of course it is in .net because they came up with the pattern. But from the wikipedia definition it is an whole other thing than what you call a viewmodel.
It is saying you drive a car but you are actually riding a bicycle.
I like the out of the box thinking, but this is never going to become a standard.
The new example was just the one you posted earlier so that you can see how it fits the convention. Either way any blade view or partial should have one @var line at top.
It will become standard or not, let's leave it up to developers to choose. I am just happy to share my experience and ideas.
Very insightful! Type safety in Blade has always been tricky - this approach seems like a game changer for reducing runtime errors.
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...
As a side note: maybe this gives you food for thought developers.slashdot.org/story/25/0...
As for the components, Lets cover this part. Take a look at the example below.
The component class
The view for component
And fnally to use
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.
Let me target custom component, twig and antler next. Please wait for me to do a little more research.