対象バージョン: Laravel 13.x / PHP 8.3+
Blade コンポーネントを使うと、ボタンやカード、ページ全体のレイアウトを再利用可能な部品としてまとめられます。この記事では、匿名コンポーネントとクラスベースコンポーネント、スロット、属性のマージ、レイアウトコンポーネントまでを順に説明します。テンプレート継承(@extends / @section / @yield)は、対応関係だけ短く触れます。
この記事で分かること
resources/views/componentsに置く匿名コンポーネントの作り方と、ドット記法によるネスト@props、スロット、$attributes->merge()/$attributes->class()の使い方- レイアウト用コンポーネントでページを包む方法
php artisan make:componentでクラスベースコンポーネントを作る方法- テンプレート継承との対応関係(
@parentの注意を含む) - Feature テストで描画結果を確認する方法
前提
Laravel 13.x は PHP 8.3 以上が必要です。動作確認は laravel/framework v13.36.0 で行いました。インストールやルーティング、Blade の基本({{ }} による表示)は【PHP】Laravel入門:インストールから簡単なサンプルアプリケーションの作成を参照してください。
メール用の <x-mail::message> などは Mailable のテンプレート向けです。メール送信そのものは【PHP】LaravelでMailableを使ってメールを送信する方法を、通知まわりは【PHP】Laravelで通知(Notifications)を実装する方法を参照してください。
匿名コンポーネントを作る
匿名コンポーネントは、クラスを持たない単一の Blade ファイルです。resources/views/components/ に置けば、そのファイル名でタグとして呼び出せます。
resources/views/components/button.blade.php を次のように書きます。
@props(['label', 'variant' => 'primary'])
<button {{ $attributes->merge(['class' => 'btn btn-'.$variant, 'type' => 'button']) }}>{{ $label }}</button>
呼び出し側は次のとおりです。
<x-button label="Save" />
<x-button label="Delete" variant="danger" class="ms-2" id="delete-btn" type="submit" />
@props に列挙したキーはデータ変数になります。キーに値を付けるとその値がデフォルトです。@props に無い属性は $attributes バッグに入ります。
ネストしたディレクトリはドット記法で呼び出します。たとえば resources/views/components/card/note.blade.php は <x-card.note> です。ディレクトリと同名のファイル(components/card/card.blade.php)や components/menu/index.blade.php は、それぞれ <x-card> / <x-menu> として使えます。
クラスなしの Blade だけを作る場合は、次のコマンドも使えます。
php artisan make:component forms.input --view
resources/views/components/forms/input.blade.php だけが作られ、<x-forms.input /> で呼び出せます。
属性の渡し方
文字列はそのまま属性として渡します。変数や式を渡すときは、属性名の前にコロンを付けます。
<x-alert type="error" :message="$message" class="mt-4" />
<x-card class="mb-4" :active="true">
type="error" は文字列 "error" です。:message="$message" は PHP の $message の値を渡します。:active="true" は真偽値の true を渡します。
スロット
x-slot タグに入っていない内容は、コンポーネント側で {{ $slot }} として受け取れます。名前付きスロットは <x-slot:title>...</x-slot> のように書き、コンポーネント側では $title として使えます。
カードの例です。
resources/views/components/card/card.blade.php:
@props(['active' => false])
<section {{ $attributes->class(['card', 'card-active' => $active]) }}>
<h2 class="card-title">{{ $title }}</h2>
<div class="card-body">{{ $slot }}</div>
</section>
呼び出し側:
<x-card class="mb-4" :active="true">
<x-slot:title>Card heading</x-slot>
Card body content
<x-card.note data-role="note">Nested note</x-card.note>
</x-card>
属性のマージ(class とそれ以外)
{{ $attributes }} は、@props に含まれない追加属性をすべて出力します。デフォルトを足したいときは $attributes->merge() を使います。
ここで重要なのは、class とそれ以外の属性で挙動が違う点です。
class: コンポーネント側のクラスと呼び出し側のクラスが結合されます。上のボタン例では、デフォルトのbtn btn-dangerに呼び出し側のms-2が付いてclass="btn btn-danger ms-2"になります。typeなどの非 class 属性:merge()で渡した値はデフォルトです。呼び出し側が同じ属性を指定すると、呼び出し側の値に置き換わります。ボタン例ではデフォルトtype="button"に対し、呼び出し側のtype="submit"が使われます。
条件付きでクラスを足すときは $attributes->class() が便利です。キーにクラス名、値に真偽値を書くと、真のときだけそのクラスが付きます。そのうえで呼び出し側のクラスも結合されます。上のカード例では :active="true" と class="mb-4" により、描画結果は class="card card-active mb-4" になります。
レイアウトコンポーネント
ページ共通のヘッダやフッタは、レイアウト用コンポーネントにまとめるのが分かりやすいやり方です。
resources/views/components/layout.blade.php:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>{{ $title ?? 'Blade Demo' }}</title>
</head>
<body>
<header id="site-header">My Blade Site</header>
<main>
{{ $slot }}
</main>
<footer id="site-footer">Footer text</footer>
</body>
</html>
ページ側は、レイアウトで包むだけです。
<x-layout>
<x-slot:title>
Components Demo
</x-slot>
<x-button label="Save" />
{{-- 他のコンポーネント --}}
</x-layout>
<x-slot:title> を渡すとそのタイトルが使われ、省略すると 'Blade Demo' が使われます。ルートから view('demo', ['message' => '...']) のように返すと、HTTP 200 の HTML としてレイアウトと部品がまとめて返ります。
クラスベースコンポーネント
ロジックを PHP クラスに置きたいときは、クラスベースコンポーネントを使います。
php artisan make:component Alert
次の2ファイルが作られます。
app/View/Components/Alert.php(App\View\Components、Illuminate\View\Componentを継承、render()がview('components.alert')を返す)resources/views/components/alert.blade.php
コンストラクタの public プロパティは、ビュー側で同名の変数として使えます。
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Alert extends Component
{
public function __construct(
public string $type,
public string $message,
) {}
public function render(): View|Closure|string
{
return view('components.alert');
}
}
ビュー側:
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
{{ $message }}
</div>
呼び出し:
<x-alert type="error" :message="$message" class="mt-4" />
描画結果の例は class="alert alert-error mt-4" です。app/View/Components と resources/views/components 配下のコンポーネントは自動で発見されるため、別途登録する必要はありません。
テンプレート継承との対応(参考)
Laravel には、コンポーネント以前から使われてきたテンプレート継承もあります。対応関係だけ押さえておきます。
| コンポーネント | テンプレート継承 |
|---|---|
<x-layout>...</x-layout> |
@extends('layouts.app') |
{{ $slot }} / 名前付きスロット |
@yield('content') / @section |
| (レイアウト側の既定セクション相当) | @section('sidebar') ... @show と子の @parent |
レイアウト側の例:
<html>
<head>
<title>App Name - @yield('title')</title>
</head>
<body>
@section('sidebar')
This is the master sidebar.
@show
<div class="container">
@yield('content')
</div>
</body>
</html>
子ビュー側:
@extends('layouts.app')
@section('title', 'Page Title')
@section('sidebar')
@parent
<p>This is appended to the master sidebar.</p>
@endsection
@section('content')
<p>This is my body content.</p>
@endsection
子のセクション内で @parent を書くと、レイアウト側の同名セクション内容のあとに子の内容が続きます。公式ドキュメントの表示ではエスケープのため @@parent と見えることがありますが、Blade ファイルに書くディレクティブは @parent(@ は1つ)です。@@parent をそのまま書くと、画面上に @parent という文字が出るだけで継承は動きません。
新規の画面ではコンポーネントを主に使い、既存の @extends ベースの画面を直すときだけ継承を使う、という切り分けで十分なことが多いです。
描画結果をテストする
Feature テストでは、ルートへ get() して HTML に期待文字列が含まれるかを見ると、レイアウトと部品の組み合わせを確認しやすいです。
<?php
namespace Tests\Feature;
use Tests\TestCase;
class BladeComponentsTest extends TestCase
{
public function test_demo_page_renders_layout_and_components(): void
{
$response = $this->get('/demo');
$response->assertStatus(200);
$response->assertSee('<title>Components Demo</title>', false);
$response->assertSee('My Blade Site');
$response->assertSee('class="btn btn-danger ms-2"', false);
$response->assertSee('class="alert alert-error mt-4"', false);
}
public function test_layout_uses_default_title_without_slot(): void
{
$this->get('/plain')->assertOk()->assertSee('<title>Blade Demo</title>', false);
}
}
assertSee の第2引数に false を渡すと、HTML タグをエスケープせずに照合できます。テスト全般の進め方は【PHP】Laravelでテストを実装する方法を参照してください。
php artisan test
まとめ
- 匿名コンポーネントは
resources/views/componentsに Blade を置くだけで<x-...>として使える。ネストはドット記法 @propsでデータ変数を宣言し、それ以外は$attributesへ入る。式は:属性名で渡す- 既定のスロットは
{{ $slot }}、名前付きは<x-slot:名前>→$名前 $attributes->merge()ではclassが結合され、typeなどの非 class 属性は呼び出し側で上書きされる- レイアウトもコンポーネント(
<x-layout>)として書ける。クラスベースはmake:componentで PHP クラスとビューが対になる - テンプレート継承は短く対応づける程度でよい。
@parentは@を1つだけ書く - テストは
get()とassertSeeで描画結果を確認する
関連記事
- 【PHP】Laravel入門:インストールから簡単なサンプルアプリケーションの作成
- 【PHP】Laravelでテストを実装する方法
- 【PHP】LaravelでMailableを使ってメールを送信する方法
- 【PHP】Laravelで通知(Notifications)を実装する方法