All rules

Sheath rule

blade-prefer-component-tags

Prefer <x-component> tag syntax over the @component directive.
Package
Core
Category
Blade
Default severity
info by default
Auto-fix
Auto-fix available

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 the resources/views/alert.blade.php view
  • <x-alert> renders App\View\Components\Alert if it exists, and otherwise resources/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\ComponentSlot rather than a string. It renders the same, but is_string() on one will not pass.
  • After conversion, an empty component can use self-closing syntax. See blade-component-self-closing.

#Related Rules