Blade Components

Blade Components, Props, and Attributes

The product card is an anonymous component: one file in resources/views/components whose @props line declares its inputs. :product="$product" passes a PHP expression and a plain attribute passes a string. Anything that is not a prop lands in $attributes, which is how data-id reached the <article> in Layouts with Components:

resources/views/components/product-card.blade.phpPHP
@props(['product', 'featured' => false])
<article {{ $attributes->class(['featured' => $featured]) }}>
  <strong>{{ $product->title }}</strong><br><small>{{ $product->author }}</small>
  <p><x-price :cents="$product->price_cents" />
  @if ($product->stock === 0) <mark>Sold out</mark>
  @elseif ($product->stock < 5) <small>Only {{ $product->stock }} left</small> @endif</p>
</article>
@pushOnce('styles') <style>.featured { outline: 3px solid #ef6c00; }</style> @endPushOnce

$attributes->merge([...]) sets overridable defaults (a caller's class is appended). A component with logic gets a class: php artisan make:component Price writes app/View/Components/Price.php and its view; constructor arguments become props, and public members become template variables, so the view is just <strong {{ $attributes }}>{{ $formatted() }}</strong>:

The body of app/View/Components/Price.phpPHP
public function __construct(public int $cents, public string $currency = 'USD') {}
public function formatted(): string
{
    return Number::currency($this->cents / 100, in: $this->currency);
}

Props are camelCase in PHP and kebab-case in tags. Beware a missing colon: <x-product-card :product="$p" featured="false" class="compact" /> rendered <article class="featured compact">, because "false" is a non-empty, truthy string.