Sheath rule
blade-prefer-component-tags
<x-component> tag syntax over the @component directive.
for names that resolve the same way. See Auto-fix.
Part of the migration preset.
#Why
@component is how components were rendered before Laravel 7. Tag syntax is what
Laravel documents now, and it is what Laravel's own markdown mail templates were
moved to. Tags also read as markup, which is what they produce.
@component still works. This migration check helps convert existing templates
to component tags.
#Examples
#Bad
@component('mail::message')
# Order shipped
@component('mail::button', ['url' => $order->url])
Track it
@endcomponent
@endcomponent
#Good
<x-mail::message>
# Order shipped
<x-mail::button :url="$order->url">
Track it
</x-mail::button>
</x-mail::message>
#Auto-fix
The fix rewrites the directive pair, the data array, and any @slot blocks
belonging to it, in one edit. Everything else in the body is carried over
verbatim, so indentation and content are untouched.
{{-- before --}}
@component('mail::message')
@slot('title')
Order shipped
@endslot
Your order is on its way.
@endcomponent
{{-- after --}}
<x-mail::message>
<x-slot:title>
Order shipped
</x-slot>
Your order is on its way.
</x-mail::message>
Nested @component blocks are converted along with their parent, so a markdown
mailable becomes tag syntax throughout in a single pass.
#The name has to resolve the same way
The two forms do not look components up in the same place. @component renders
a view. Laravel resolves a component tag to a class first and falls back to
a view.
@component('alert')renders theresources/views/alert.blade.phpview<x-alert>rendersApp\View\Components\Alertif it exists, and otherwiseresources/views/components/alert.blade.php
Sheath rewrites only names that resolve to the same file in both forms.
| Written as | Rewritten to | Why |
|---|---|---|
@component('components.alert') |
<x-alert> |
Already pointing at the directory tags look in |
@component('components.forms.input') |
<x-forms.input> |
Same, one directory deeper |
@component('mail::message') |
<x-mail::message> |
Laravel's component resolution falls back to the mail:: view name |
@component('alert') |
not rewritten | Would start resolving to a different file |
@component('admin::alert') |
not rewritten | See below |
@component($name) |
not rewritten | Not knowable from the template |
mail:: is the only namespace prefix that converts because Laravel resolves it
back to a view name. For any other prefix,
@component('admin::alert') uses the view namespace registered with
View::addNamespace, while <x-admin::alert> needs a component namespace
registered with Blade::componentNamespace or Blade::anonymousComponentNamespace.
Those are different registrations, and without the second one the tag does not
resolve at all. Laravel throws Unable to locate a class or view for component.
A @component that cannot be rewritten is still reported with the reason.
Rewriting it would require moving the view file or changing a registration.
#Every component-tag rewrite is dangerous
Every fix is marked dangerous and only runs under --fix --dangerous.
Component resolution depends on application registrations and can change even
when the directive passes no data. When a fix writes attributes, two additional
things can differ once data is passed as attributes.
An undeclared attribute is also left in the bag. @component passes data and
nothing else. An attribute that no @props claims becomes a variable and stays
in $attributes, so a component that spreads its bag renders an attribute it did
not render before:
{{-- resources/views/components/alert.blade.php --}}
<div {{ $attributes }}>{{ $level }}</div>
<!-- @component('components.alert', ['level' => 'error']) -->
<div >error</div>
<!-- <x-alert level="error"> without @props -->
<div level="error">error</div>
Declaring the prop removes it from the bag and makes the two identical:
@props(['level'])
Laravel prefers a class-based component of the same name. If the application
defines App\View\Components\Alert, <x-alert> renders that class instead of
the view @component('components.alert') was rendering. The data then has to fit
the class's constructor.
Values are converted by kind, so nothing changes type:
| Written as | Becomes | |
|---|---|---|
['level' => 'error'] |
level="error" |
A plain string stays a plain string |
['count' => 3] |
:count="3" |
Bound, so it stays an integer |
['url' => $order->url] |
:url="$order->url" |
Bound expression |
['url' => route("show")] |
:url='route("show")' |
Single-quoted so the inner quotes survive |
A component with no data array is still dangerous because the generated tag can resolve to a different class or view under the application's registrations.
#When it reports but will not rewrite
These component calls require a manual rewrite because a tag would change or obscure their attributes.
| Shape | Why |
|---|---|
A data key that is not already camelCase, such as foo_bar or foo-bar |
Laravel camel-cases attribute names, so the prop would be renamed |
| A key that is not a literal string, or a spread | The attribute names are not knowable |
| A data argument that is not a literal array | Same |
| A value expression containing both quote characters | It cannot be placed in an attribute |
| The same key twice | The second would silently win |
@slot('title', $value) |
The two-argument form sets a slot from an expression, which is a different tag |
| A slot name that is not already camelCase | Same renaming problem as data keys |
A @slot that cannot be converted blocks the whole component because @slot
is valid only inside @component. A nested @component that cannot be
converted does not block its parent because it keeps working inside a tag
body.
#Notes
- Named slots arrive as
Illuminate\View\ComponentSlotrather than a string. It renders the same, butis_string()on one will not pass. - After conversion, an empty component can use self-closing syntax. See blade-component-self-closing.
#Related Rules
- blade-component-self-closing - empty components should self-close
- blade-require-props - anonymous components should declare
@props