Blade 템플릿

번역일: 2026년 7월 2일

Blade 템플릿

소개

Blade는 Laravel에 내장된 심플하지만 강력한 템플릿 엔진입니다. 일부 PHP 템플릿 엔진과 달리, Blade는 뷰 안에서 순수 PHP 코드를 그대로 사용하는 것을 막지 않습니다. 실제로 모든 Blade 뷰는 PHP 코드로 컴파일되어 캐싱됩니다. 캐시는 뷰 파일이 변경될 때만 갱신되므로, Blade로 인한 성능 부담은 사실상 없다고 볼 수 있습니다.

Blade 뷰 파일의 확장자는 .blade.php이며, 보통 resources/views 디렉터리에 보관합니다. 컨트롤러나 라우트에서 view() 헬퍼를 통해 반환할 수 있습니다.

Route::get('/', function () { return view('greeting', ['name' => '철수']); });

Livewire로 Blade 확장하기

Blade를 한 단계 더 발전시키고 싶다면 Laravel Livewire를 살펴보세요. Livewire를 사용하면 JavaScript 프레임워크 없이도 Blade 컴포넌트에 동적인 인터랙션을 추가할 수 있습니다. SPA와 같은 사용자 경험을 복잡한 클라이언트 사이드 코드 없이 구현할 수 있어, 많은 팀에서 현대적인 반응형 UI를 구축하는 데 활용하고 있습니다.

데이터 출력

뷰에 전달된 변수는 이중 중괄호({{ }})로 출력할 수 있습니다. 예를 들어 다음과 같은 라우트가 있다면:

Route::get('/', function () { return view('welcome', ['name' => '철수']); });

뷰에서 name 변수를 아래와 같이 출력합니다:

안녕하세요, {{ $name }}.

NOTE

Blade의 {{ }} 출력 구문은 XSS 공격 방지를 위해 PHP의 htmlspecialchars 함수를 자동으로 적용합니다.

Blade는 뷰에 전달된 변수만 출력할 수 있는 것이 아닙니다. PHP 함수의 반환값도 바로 출력할 수 있습니다. {{ }} 안에는 어떤 PHP 표현식이든 사용 가능합니다:

현재 시각: {{ date('Y-m-d H:i:s') }}

HTML 엔티티 인코딩

기본적으로 Blade(그리고 Laravel의 e 헬퍼)는 HTML 엔티티를 이중으로 인코딩합니다. 이중 인코딩을 비활성화하려면 AppServiceProviderboot 메서드에서 Blade::withoutDoubleEncoding을 호출하세요:

<?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * 애플리케이션 서비스 부트스트랩. */ public function boot(): void { Blade::withoutDoubleEncoding(); } }

이스케이프 없이 출력하기

기본적으로 {{ }} 구문은 HTML을 자동으로 이스케이프합니다. 이스케이프 없이 원본 HTML을 그대로 출력하려면 {!! !!} 구문을 사용하세요:

안녕하세요, {!! $name !!}.

WARNING

{!! !!}를 사용할 때는 반드시 주의하세요. 사용자가 입력한 값을 그대로 출력하면 XSS 취약점이 생길 수 있습니다. 신뢰할 수 있는 데이터에만 사용하는 것을 권장합니다.

Blade와 JavaScript 프레임워크

Vue나 Alpine.js 같은 JavaScript 프레임워크도 이중 중괄호({{ }})를 사용해 변수를 출력하는 경우가 많습니다. 이때 @ 기호를 앞에 붙이면 Blade가 해당 표현식을 처리하지 않고 그대로 남겨두도록 지시할 수 있습니다:

<h1>Laravel</h1> 안녕하세요, @{{ name }}.

위 예제에서 @ 기호는 Blade가 제거하지만, {{ name }} 부분은 JavaScript 엔진이 처리할 수 있도록 그대로 렌더링됩니다.

@ 기호는 Blade 디렉티브 앞에도 사용할 수 있습니다:

{{-- Blade 뷰 --}} @@if() <!-- HTML 출력 --> @if()

JSON 렌더링

JavaScript 변수를 초기화하기 위해 배열을 JSON으로 변환해 출력할 때가 있습니다. 예를 들면:

<script> var app = <?php echo json_encode($array); ?>; </script>

이 방법 대신 @json 디렉티브를 사용하면 더 깔끔하게 작성할 수 있습니다. @json 디렉티브는 PHP의 json_encode 함수와 동일한 인수를 받습니다:

<script> var app = @json($array); var app = @json($array, JSON_PRETTY_PRINT); </script>

WARNING

@json은 기존 변수를 JSON으로 렌더링하는 용도로만 사용하세요. Blade 템플릿은 정규 표현식 기반으로 동작하므로, 복잡한 표현식을 디렉티브에 전달하면 예기치 않은 문제가 발생할 수 있습니다.

`@verbatim` 디렉티브

뷰의 넓은 범위에서 JavaScript 변수를 출력해야 할 경우, @verbatim 디렉티브로 해당 영역을 감싸면 각 Blade 출력 구문마다 @를 붙이지 않아도 됩니다:

@verbatim <div class="container"> 안녕하세요, {{ name }}. </div> @endverbatim

Blade 디렉티브

Blade는 {{ }}를 이용한 템플릿 출력 외에도, 조건문과 반복문 같은 일반적인 PHP 제어 구조를 위한 간편한 단축 문법(디렉티브)을 제공합니다. 이 단축 문법은 PHP 제어 구조와 동일하게 동작하면서도 훨씬 깔끔하고 읽기 쉽습니다.

조건문

@if, @elseif, @else, @endif 디렉티브로 조건문을 작성합니다. PHP의 if 문과 동일하게 동작합니다:

@if (count($records) === 1) 레코드가 1개 있습니다. @elseif (count($records) > 1) 레코드가 여러 개 있습니다. @else 레코드가 없습니다. @endif

편의를 위해 @unless 디렉티브도 제공됩니다:

@unless (Auth::check()) 로그인이 필요합니다. @endunless

앞서 소개한 디렉티브 외에도, @isset@empty 디렉티브를 각각 동일한 이름의 PHP 함수 단축 표현으로 사용할 수 있습니다:

@isset($records) // $records가 정의되어 있고 null이 아닌 경우 @endisset @empty($records) // $records가 비어 있는 경우 @endempty

인증 디렉티브

@auth@guest 디렉티브를 사용하면 현재 사용자가 인증되었는지, 혹은 비로그인 상태인지 빠르게 확인할 수 있습니다:

@auth // 로그인된 사용자 @endauth @guest // 비로그인 사용자 @endguest

필요하다면 @auth@guest에 특정 인증 가드를 지정할 수 있습니다:

@auth('admin') // 관리자로 로그인된 사용자 @endauth @guest('admin') // 관리자로 로그인되지 않은 사용자 @endguest

환경 디렉티브

@production 디렉티브로 애플리케이션이 프로덕션 환경에서 실행 중인지 확인할 수 있습니다:

@production // 프로덕션 환경에서만 표시되는 내용 @endproduction

특정 환경을 직접 지정하려면 @env 디렉티브를 사용하세요:

@env('staging') // 스테이징 환경에서 실행 중입니다. @endenv @env(['staging', 'production']) // 스테이징 또는 프로덕션 환경에서 실행 중입니다. @endenv

섹션 디렉티브

@hasSection 디렉티브로 특정 템플릿 상속 섹션에 내용이 있는지 확인할 수 있습니다:

@hasSection('navigation') <div class="pull-right"> @yield('navigation') </div> <div class="clearfix"></div> @endif

반대로 섹션에 내용이 없을 때를 확인하려면 @sectionMissing을 사용하세요:

@sectionMissing('navigation') <div class="pull-right"> @include('default-navigation') </div> @endif

세션 디렉티브

@session 디렉티브를 사용하면 세션에 특정 값이 존재하는지 확인할 수 있습니다. 세션 값이 있으면 @session@endsession 사이의 내용이 렌더링되며, 해당 블록 안에서는 $value 변수로 세션 값에 접근할 수 있습니다:

@session('status') <div class="p-4 bg-green-100"> {{ $value }} </div> @endsession

Switch 문

@switch, @case, @break, @default, @endswitch 디렉티브로 Switch 문을 작성합니다:

@switch($i) @case(1) 첫 번째 케이스 @break @case(2) 두 번째 케이스 @break @default 기본 케이스 @endswitch

반복문

Blade는 PHP의 반복문 구조를 위한 디렉티브도 제공합니다. 각 디렉티브는 대응하는 PHP 반복문과 동일하게 동작합니다:

@for ($i = 0; $i < 10; $i++) 현재 값: {{ $i }} @endfor @foreach ($users as $user) <p>사용자: {{ $user->id }}</p> @endforeach @forelse ($users as $user) <li>{{ $user->name }}</li> @empty <p>사용자가 없습니다.</p> @endforelse @while (true) <p>무한 루프 중입니다.</p> @endwhile

NOTE

@foreach 반복문 안에서는 루프 변수를 활용해 반복의 첫 번째/마지막 여부 등 유용한 정보를 확인할 수 있습니다.

반복문을 사용할 때 @continue@break 디렉티브로 현재 반복을 건너뛰거나 반복문을 종료할 수 있습니다:

@foreach ($users as $user) @if ($user->type == 1) @continue @endif <li>{{ $user->name }}</li> @if ($user->number == 5) @break @endif @endforeach

디렉티브 안에 조건을 바로 포함시켜 더 간결하게 작성할 수도 있습니다:

@foreach ($users as $user) @continue($user->type == 1) <li>{{ $user->name }}</li> @break($user->number == 5) @endforeach

루프 변수

@foreach 반복문 안에서는 $loop 변수를 자동으로 사용할 수 있습니다. 이 변수는 현재 반복의 인덱스, 첫/마지막 여부 등 여러 유용한 정보를 제공합니다:

@foreach ($users as $user) @if ($loop->first) 첫 번째 반복입니다. @endif @if ($loop->last) 마지막 반복입니다. @endif <p>사용자: {{ $user->id }}</p> @endforeach

반복문이 중첩된 경우, $loop->parent를 통해 부모 반복문의 $loop 변수에 접근할 수 있습니다:

@foreach ($users as $user) @foreach ($user->posts as $post) @if ($loop->parent->first) 첫 번째 사용자의 첫 번째 포스트입니다. @endif @endforeach @endforeach

$loop 변수가 제공하는 전체 프로퍼티 목록은 다음과 같습니다:

프로퍼티설명
$loop->index현재 반복의 인덱스 (0부터 시작)
$loop->iteration현재 반복 횟수 (1부터 시작)
$loop->remaining남은 반복 횟수
$loop->count반복 대상 배열의 전체 항목 수
$loop->first첫 번째 반복 여부
$loop->last마지막 반복 여부
$loop->even현재 반복이 짝수 번째인지 여부
$loop->odd현재 반복이 홀수 번째인지 여부
$loop->depth현재 반복문의 중첩 깊이
$loop->parent중첩 반복문에서 부모의 루프 변수

조건부 클래스

@class 디렉티브는 조건에 따라 CSS 클래스를 동적으로 적용할 때 사용합니다. 배열을 인수로 받으며, 키는 클래스명, 값은 적용 여부를 결정하는 불리언 표현식입니다. 값이 숫자 키인 경우 해당 클래스는 항상 적용됩니다:

@php $isActive = false; $hasError = true; @endphp <span @class([ 'p-4', 'font-bold' => $isActive, 'text-gray-500' => ! $isActive, 'bg-red' => $hasError, ])></span>

위 코드는 다음과 같이 렌더링됩니다:

<span class="p-4 text-gray-500 bg-red"></span>

추가 속성

@style 디렉티브를 사용하면 조건에 따라 인라인 CSS 스타일을 동적으로 적용할 수 있습니다:

@php $isActive = true; @endphp <span @style([ 'background-color: red', 'font-weight: bold;' => $isActive, ])></span>

렌더링 결과:

<span style="background-color: red; font-weight: bold;"></span>

@checked 디렉티브는 HTML 체크박스가 "checked" 상태인지 편리하게 표현할 수 있습니다. 조건이 true이면 checked를 출력합니다:

<input type="checkbox" name="active" value="active" @checked(old('active', $user->active)) />

@selected 디렉티브는 select 옵션이 선택된 상태인지 나타낼 때 사용합니다:

<select name="version"> @foreach ($product->versions as $version) <option value="{{ $version }}" @selected(old('version') == $version)> {{ $version }} </option> @endforeach </select>

@disabled 디렉티브는 요소의 비활성화 여부를 나타냅니다:

<button type="submit" @disabled($errors->isNotEmpty())>제출</button>

@readonly 디렉티브는 입력 필드의 읽기 전용 여부를 나타냅니다:

<input type="email" name="email" value="이메일@예시.com" @readonly($user->isNotAdmin()) />

@required 디렉티브는 입력 필드의 필수 여부를 나타냅니다:

<input type="text" name="title" value="제목" @required($user->isAdmin()) />

하위 뷰 포함

NOTE

@include 디렉티브를 자유롭게 사용할 수 있지만, Blade 컴포넌트는 비슷한 기능을 제공하면서 데이터 바인딩과 속성 처리 등 여러 장점이 있습니다. 새로운 코드를 작성한다면 컴포넌트 방식을 권장합니다.

@include 디렉티브를 사용하면 한 Blade 뷰 안에 다른 뷰를 포함시킬 수 있습니다. 부모 뷰에서 사용 가능한 모든 변수는 포함된 하위 뷰에서도 자동으로 사용할 수 있습니다:

<div> @include('shared.errors') <form> <!-- 폼 내용 --> </form> </div>

포함된 뷰는 부모 뷰의 모든 변수를 상속하지만, 추가로 전달할 데이터를 배열로 지정할 수도 있습니다:

@include('view.name', ['status' => '완료'])

존재하지 않는 뷰를 @include하면 오류가 발생합니다. 뷰가 있을 수도 없을 수도 있는 상황이라면 @includeIf를 사용하세요:

@includeIf('view.name', ['status' => '완료'])

불리언 조건이 true 또는 false일 때만 뷰를 포함하려면 @includeWhen@includeUnless를 사용하세요:

@includeWhen($boolean, 'view.name', ['status' => '완료']) @includeUnless($boolean, 'view.name', ['status' => '완료'])

주어진 뷰 배열 중 첫 번째로 존재하는 뷰를 포함하려면 @includeFirst를 사용하세요:

@includeFirst(['custom.admin', 'admin'], ['status' => '완료'])

WARNING

Blade 뷰에서 __DIR____FILE__ 상수 사용을 피하세요. 이 상수들은 컴파일된 캐시 파일의 경로를 참조하게 됩니다.

컬렉션 뷰 렌더링

반복과 뷰 포함을 한 줄로 결합하려면 @each 디렉티브를 사용하세요:

@each('view.name', $jobs, 'job')

첫 번째 인수는 각 항목을 렌더링할 뷰, 두 번째 인수는 반복할 배열 또는 컬렉션, 세 번째 인수는 뷰 안에서 사용할 변수명입니다. 예를 들어 jobs 배열을 반복하면, 뷰 안에서 각 항목을 $job 변수로 접근할 수 있습니다. 현재 반복의 배열 키는 뷰 안에서 key 변수로 사용할 수 있습니다.

배열이 비어 있을 때 렌더링할 뷰를 네 번째 인수로 지정할 수도 있습니다:

@each('view.name', $jobs, 'job', 'view.empty')

WARNING

@each로 렌더링된 뷰는 부모 뷰의 변수를 상속하지 않습니다. 하위 뷰에서 부모 변수가 필요하다면 @foreach@include를 조합해 사용하세요.

`@once` 디렉티브

@once 디렉티브를 사용하면 특정 템플릿 코드를 페이지 렌더링 사이클 당 딱 한 번만 실행할 수 있습니다. 예를 들어 반복문 안에서 특정 JavaScript를 페이지 헤더에 한 번만 삽입하고 싶을 때 유용합니다:

@foreach ($products as $product) @once @push('scripts') <script> // 초기화 스크립트... </script> @endpush @endonce @endforeach

@once 디렉티브는 @push@prepend와 함께 자주 사용되므로, 이를 편리하게 합친 @pushOnce@prependOnce 디렉티브도 제공됩니다:

@pushOnce('scripts') <script> // 초기화 스크립트... </script> @endPushOnce

Raw PHP

뷰 안에서 PHP 코드를 직접 실행해야 할 때는 @php 디렉티브를 사용하세요:

@php $counter = 1; @endphp

클래스만 import할 목적이라면 @use 디렉티브를 사용할 수 있습니다:

@use('App\Models\Flight')

@use 디렉티브에 두 번째 인수를 전달해 import한 클래스에 별칭을 지정할 수 있습니다:

@use('App\Models\Flight', 'FlightModel')

주석

Blade 주석은 HTML로 렌더링되지 않으므로, 실제 HTML 소스에 포함되지 않습니다:

{{-- 이 주석은 렌더링된 HTML에 포함되지 않습니다. --}}

컴포넌트

컴포넌트와 슬롯은 섹션, 레이아웃, 포함(include)과 유사한 기능을 제공하지만, 컴포넌트의 사고 방식이 더 직관적으로 느껴지는 경우도 있습니다. 컴포넌트 작성 방식에는 클래스 기반 컴포넌트와 익명 컴포넌트 두 가지가 있습니다.

클래스 기반 컴포넌트를 만들려면 make:component Artisan 명령을 실행하세요. 사용법을 익히기 위해 간단한 Alert 컴포넌트를 만들어 보겠습니다:

php artisan make:component Alert

이 명령은 app/View/Components 디렉터리에 컴포넌트 클래스를 생성하고, resources/views/components 디렉터리에 해당 뷰 파일을 함께 생성합니다.

하위 디렉터리 안에 컴포넌트를 만들 수도 있습니다:

php artisan make:component Forms/Input

이 명령은 app/View/Components/Forms 디렉터리에 Input 클래스를, resources/views/components/forms 디렉터리에 뷰 파일을 생성합니다.

PHP 클래스 없이 Blade 파일만으로 구성된 익명 컴포넌트를 만들려면 make:component 명령에 --view 플래그를 사용하세요:

php artisan make:component forms.input --view

이 명령은 resources/views/components/forms/input.blade.php 파일만 생성하며, <x-forms.input />으로 렌더링할 수 있습니다.

패키지 컴포넌트 수동 등록

패키지 개발 시에는 컴포넌트를 수동으로 등록해야 할 경우가 있습니다. 보통 서비스 프로바이더의 boot 메서드에서 등록합니다:

use Illuminate\Support\Facades\Blade; /** * 패키지 서비스 부트스트랩. */ public function boot(): void { Blade::component('package-alert', Alert::class); }

등록 후에는 태그 별칭으로 렌더링할 수 있습니다:

<x-package-alert/>

또는 componentNamespace 메서드를 사용해 컨벤션에 따라 컴포넌트 클래스를 자동으로 로드할 수 있습니다. 예를 들어 Nightshade 패키지에 Package\Views\Components 네임스페이스 하에 CalendarColorPicker 컴포넌트가 있다면:

use Illuminate\Support\Facades\Blade; /** * 패키지 서비스 부트스트랩. */ public function boot(): void { Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); }

이렇게 등록하면 패키지명::컴포넌트명 형식으로 컴포넌트를 사용할 수 있습니다:

<x-nightshade::calendar /> <x-nightshade::color-picker />

Blade는 컴포넌트 이름을 파스칼 케이스로 변환해 해당 클래스를 자동으로 찾습니다. 하위 디렉터리도 "점 표기법"으로 지원합니다.

컴포넌트 렌더링

컴포넌트를 표시하려면 Blade 템플릿 안에서 <x- 접두사에 컴포넌트 클래스 이름(케밥 케이스)을 붙여 사용합니다:

<x-alert/> <x-user-profile/>

app/View/Components 디렉터리의 하위 폴더에 있는 컴포넌트는 점 표기법으로 렌더링합니다:

<x-inputs.button/>

컴포넌트를 조건부로 렌더링하고 싶다면, 컴포넌트 클래스에 shouldRender 메서드를 정의하세요. 이 메서드가 false를 반환하면 컴포넌트가 렌더링되지 않습니다:

use Illuminate\Support\Facades\Auth; /** * 컴포넌트 렌더링 여부. */ public function shouldRender(): bool { return Auth::user()->isAdmin(); }

컴포넌트에 데이터 전달

HTML 속성 방식으로 컴포넌트에 데이터를 전달할 수 있습니다. 단순 문자열 같은 기본값은 일반 HTML 속성으로 전달하고, PHP 표현식이나 변수는 : 접두사를 붙인 속성으로 전달합니다:

<x-alert type="error" :message="$message"/>

컴포넌트의 모든 데이터 속성은 클래스 생성자에서 정의해야 합니다. 컴포넌트의 public 프로퍼티는 뷰에서 자동으로 사용할 수 있으며, render 메서드에서 별도로 전달할 필요가 없습니다:

<?php namespace App\View\Components; use Illuminate\View\Component; use Illuminate\View\View; class Alert extends Component { /** * 컴포넌트 인스턴스 생성. */ public function __construct( public string $type, public string $message, ) {} /** * 컴포넌트를 표현하는 뷰 반환. */ public function render(): View { return view('components.alert'); } }

컴포넌트 뷰에서는 다음과 같이 사용합니다:

<div class="alert alert-{{ $type }}"> {{ $message }} </div>

케이스 표기법

컴포넌트 생성자 인수는 카멜 케이스(camelCase)로 작성하지만, HTML 속성에서는 케밥 케이스(kebab-case)로 참조합니다:

/** * 컴포넌트 인스턴스 생성. */ public function __construct( public string $alertType, ) {}
<x-alert alert-type="danger" />

단축 속성 문법

컴포넌트에 속성을 전달할 때, "단축 속성" 문법을 사용할 수 있습니다. 변수명과 속성명이 동일한 경우가 많으므로 편리합니다:

{{-- 단축 속성 문법 --}} <x-profile :$userId :$name /> {{-- 위와 동일한 표현 --}} <x-profile :user-id="$userId" :name="$name" />

속성 렌더링 이스케이프

Alpine.js처럼 콜론(:)을 속성 접두사로 사용하는 JavaScript 프레임워크를 사용할 경우, :: 이중 콜론으로 Blade가 해당 속성을 PHP 표현식으로 처리하지 않도록 할 수 있습니다:

<x-button ::class="{ danger: isDeleting }"> 제출 </x-button>

렌더링 결과:

<button :class="{ danger: isDeleting }"> 제출 </button>

컴포넌트 메서드

컴포넌트 템플릿에서는 public 프로퍼티뿐만 아니라 public 메서드도 호출할 수 있습니다. 예를 들어 isSelected 메서드가 있는 컴포넌트에서:

/** * 주어진 옵션이 현재 선택된 옵션인지 확인. */ public function isSelected(string $option): bool { return $option === $this->selected; }

컴포넌트 템플릿에서는 다음과 같이 호출합니다:

<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}"> {{ $label }} </option>

컴포넌트 클래스 내에서 속성 및 슬롯 접근

컴포넌트 클래스의 render 메서드에서 클로저를 반환하면, 컴포넌트 이름, 속성, 슬롯에 접근할 수 있습니다:

use Closure; /** * 컴포넌트를 표현하는 뷰 반환. */ public function render(): Closure { return function (array $data) { // $data['componentName'] - 컴포넌트 이름 (예: "alert") // $data['attributes'] - 컴포넌트에 전달된 속성 목록 // $data['slot'] - 슬롯 내용 return '<div>{{ $slot }}</div>'; }; }

클로저가 문자열을 반환하면 Blade 템플릿으로 처리됩니다.

추가 의존성 주입

컴포넌트에 Laravel 서비스 컨테이너에서 해결해야 할 의존성이 있다면, 데이터 속성보다 앞에 나열하면 컨테이너가 자동으로 주입합니다:

use App\Services\AlertCreator; /** * 컴포넌트 인스턴스 생성. */ public function __construct( public AlertCreator $creator, public string $type, public string $message, ) {}

속성 및 메서드 숨기기

특정 public 메서드나 프로퍼티를 컴포넌트 템플릿 변수로 노출하고 싶지 않다면, $except 배열에 추가하세요:

<?php namespace App\View\Components; use Illuminate\View\Component; class Alert extends Component { /** * 컴포넌트 템플릿에 노출하지 않을 프로퍼티/메서드 목록. * * @var array */ protected $except = ['type']; /** * 컴포넌트 인스턴스 생성. */ public function __construct( public string $type, ) {} }

컴포넌트 속성

컴포넌트에 데이터 속성을 전달하는 방법은 앞서 살펴봤습니다. 그런데 컴포넌트 동작과 직접 관련 없는 추가 HTML 속성(예: class)을 전달해야 할 때도 있습니다. 이런 속성들은 컴포넌트 생성자에 정의된 것이 아니라 "속성 백(attribute bag)"으로 관리됩니다. 이 속성 백은 $attributes 변수를 통해 컴포넌트 뷰에서 접근할 수 있습니다:

<div {{ $attributes }}> <!-- 컴포넌트 내용 --> </div>

WARNING

현재 컴포넌트 태그 안에서 @env와 같은 디렉티브는 지원되지 않습니다. 예를 들어 <x-alert :live="@env('production')"/>는 컴파일되지 않습니다.

기본값과 속성 병합

기본값이 있는 속성이나 추가 값을 합쳐야 하는 경우, $attributesmerge 메서드를 사용하세요. CSS 클래스를 기본값으로 지정하는 예시입니다:

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}> {{ $message }} </div>

이 컴포넌트를 다음과 같이 사용하면:

<x-alert type="error" :message="$message" class="mb-4"/>

렌더링 결과는 다음과 같습니다:

<div class="alert alert-error mb-4"> <!-- 메시지 내용 --> </div>

조건부 클래스 병합

조건에 따라 클래스를 병합하려면 class 메서드를 사용하세요. 배열의 키는 클래스명, 값은 불리언 표현식입니다:

<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}> {{ $message }} </div>

다른 속성도 함께 병합해야 한다면 class 메서드에 merge를 체이닝하세요:

<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}> {{ $slot }} </button>

NOTE

병합된 속성을 받지 않는 다른 HTML 요소에 조건부 클래스를 적용해야 한다면, @class 디렉티브를 사용하세요.

class가 아닌 속성 병합

class가 아닌 일반 속성을 병합할 경우, merge 메서드에 지정한 값이 기본값이 됩니다. 단, class와 달리 이런 속성들은 덮어쓰는 방식으로 동작합니다:

<button {{ $attributes->merge(['type' => 'button']) }}> {{ $slot }} </button>

이 컴포넌트를 type을 지정해서 렌더링하면 해당 값으로 재정의됩니다:

<x-button type="submit"> 로그인 </x-button>

결과:

<button type="submit"> 로그인 </button>

class 이외의 속성에 기본값과 전달된 값을 모두 합치려면 prepends 메서드를 사용하세요:

<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}> {{ $slot }} </div>

속성 필터링 및 조회

filter 메서드로 속성을 필터링할 수 있습니다. 클로저에서 true를 반환하면 해당 속성이 유지됩니다:

{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}

특정 문자열로 시작하는 속성만 가져오려면 whereStartsWith를 사용하세요:

{{ $attributes->whereStartsWith('wire:model') }}

반대로 특정 문자열로 시작하지 않는 속성만 가져오려면 whereDoesntStartWith를 사용하세요:

{{ $attributes->whereDoesntStartWith('wire:model') }}

first 메서드로 속성 백의 첫 번째 속성을 렌더링할 수 있습니다:

{{ $attributes->whereStartsWith('wire:model')->first() }}

특정 속성이 있는지 확인하려면 has 메서드를 사용하세요. 속성명을 문자열로 전달하면 해당 속성의 존재 여부를 반환합니다. 배열을 전달하면 모든 속성이 존재하는지 확인합니다:

@if ($attributes->has('class')) <div>class 속성이 있습니다.</div> @endif @if ($attributes->has(['name', 'class'])) <div>name과 class 속성이 모두 있습니다.</div> @endif

hasAny 메서드는 주어진 속성 중 하나라도 있는지 확인합니다:

@if ($attributes->hasAny(['href', ':href', 'v-bind:href'])) <div>href 관련 속성이 있습니다.</div> @endif

get 메서드로 특정 속성의 값을 가져올 수 있습니다:

{{ $attributes->get('class') }}

예약 키워드

기본적으로 일부 키워드는 컴포넌트 렌더링을 위해 Blade 내부에서 사용하므로, 컴포넌트 생성자의 public 프로퍼티나 메서드 이름으로 사용할 수 없습니다:

  • data
  • render
  • resolveView
  • shouldRender
  • view
  • withAttributes
  • withName

슬롯

컴포넌트에 추가 콘텐츠를 전달할 때는 "슬롯"을 사용합니다. 컴포넌트 슬롯은 $slot 변수를 출력하는 방식으로 렌더링됩니다. 다음 alert 컴포넌트 예시를 살펴보겠습니다:

<!-- /resources/views/components/alert.blade.php --> <div class="alert alert-danger"> {{ $slot }} </div>

슬롯에 내용을 전달하려면 컴포넌트 태그 안에 콘텐츠를 작성합니다:

<x-alert> <strong>오류 발생!</strong> 문제가 생겼습니다. </x-alert>

경우에 따라 컴포넌트 내 여러 위치에 서로 다른 콘텐츠를 주입해야 할 수 있습니다. 이때는 이름이 있는 슬롯을 사용합니다. @slot 디렉티브로 명시적으로 정의하지 않은 슬롯 내용은 기본 $slot 변수에 전달됩니다:

<!-- /resources/views/components/alert.blade.php --> <span class="alert-title">{{ $title }}</span> <div class="alert alert-danger"> {{ $slot }} </div>

이름이 있는 슬롯은 <x-slot:슬롯명> 태그로 작성합니다:

<x-alert> <x-slot:title> 서버 오류 </x-slot> <strong>오류 발생!</strong> 문제가 생겼습니다. </x-alert>

슬롯의 내용이 비어 있는지 확인하려면 슬롯의 isEmpty 메서드를 사용하세요:

<span class="alert-title">{{ $title }}</span> <div class="alert alert-danger"> @if ($slot->isEmpty()) 기본 콘텐츠입니다. @else {{ $slot }} @endif </div>

hasActualContent 메서드로 슬롯에 HTML 주석을 제외한 실제 내용이 있는지 확인할 수 있습니다:

@if ($slot->hasActualContent()) 슬롯에 실제 내용이 있습니다. @endif

스코프 슬롯

Vue처럼 슬롯 안에서 컴포넌트의 데이터나 메서드에 접근해야 할 경우, 컴포넌트의 public 메서드와 프로퍼티를 슬롯에서 사용할 수 있습니다. 컴포넌트 태그에 :attributes를 지정하면 됩니다:

<x-alert> <x-slot:title> {{ $component->formatTitle($title) }} </x-slot> </x-alert>

인라인 컴포넌트 뷰

매우 간단한 컴포넌트의 경우, 별도의 뷰 파일 없이 render 메서드에서 직접 Blade 마크업 문자열을 반환할 수 있습니다:

/** * 컴포넌트를 표현하는 뷰 반환. */ public function render(): string { return <<<'blade' <div class="alert alert-danger"> {{ $slot }} </div> blade; }

인라인 뷰 컴포넌트 생성

인라인 뷰를 렌더링하는 컴포넌트를 만들려면 make:component 명령에 --inline 옵션을 사용하세요:

php artisan make:component Alert --inline

동적 컴포넌트

어떤 컴포넌트를 렌더링할지 런타임에 결정해야 할 경우, <x-dynamic-component> 태그를 사용할 수 있습니다:

// $componentName = "secondary-button" <x-dynamic-component :component="$componentName" class="mt-4" />

컴포넌트 수동 등록

WARNING

컴포넌트 수동 등록에 관한 다음 내용은 주로 뷰 컴포넌트를 포함하는 Laravel 패키지를 개발하는 경우에 해당합니다. 패키지 개발이 아니라면 이 섹션은 일반적으로 필요하지 않습니다.

자체 애플리케이션의 컴포넌트를 작성하는 경우, app/View/Componentsresources/views/components 디렉터리에 위치한 컴포넌트는 자동으로 검색됩니다.

단, Blade 컴포넌트를 사용하는 패키지를 개발하거나 비관습적인 디렉터리를 사용한다면, 컴포넌트 클래스와 HTML 태그 별칭을 수동으로 등록해야 합니다. 보통 패키지 서비스 프로바이더의 boot 메서드에서 등록합니다:

use Illuminate\Support\Facades\Blade; use VendorPackage\View\Components\AlertComponent; /** * 패키지 서비스 부트스트랩. */ public function boot(): void { Blade::component('package-alert', AlertComponent::class); }

등록 후에는 다음과 같이 사용합니다:

<x-package-alert/>

익명 컴포넌트

클래스 기반 컴포넌트와 마찬가지로, 익명 컴포넌트는 뷰 파일 하나로 UI 조각을 재사용하는 방법입니다. 단, 익명 컴포넌트는 별도의 PHP 클래스 없이 Blade 파일 하나만으로 구성됩니다. 익명 컴포넌트 파일은 resources/views/components 디렉터리에 위치합니다. 예를 들어 resources/views/components/alert.blade.php로 저장하면 다음과 같이 렌더링할 수 있습니다:

<x-alert/>

components 디렉터리의 하위 폴더에 있는 컴포넌트는 점 표기법을 사용합니다:

<x-forms.input/>

익명 인덱스 컴포넌트

여러 파일로 구성된 컴포넌트를 하나의 디렉터리로 묶어 관리하고 싶을 때가 있습니다. 예를 들어 resources/views/components/accordion/ 디렉터리에 다음 파일들이 있다고 가정합니다:

/resources/views/components/accordion/index.blade.php
/resources/views/components/accordion/item.blade.php

index.blade.php 파일은 <x-accordion />으로 렌더링할 수 있으며, 컴포넌트 전체의 진입점 역할을 합니다:

<x-accordion> <x-accordion.item> ... </x-accordion.item> </x-accordion>

데이터 프로퍼티 / 속성

익명 컴포넌트는 별도의 PHP 클래스가 없으므로, 어떤 데이터를 변수로 받고 어떤 것을 속성 백으로 처리할지 구분해야 합니다. 컴포넌트 Blade 파일 맨 위에서 @props 디렉티브를 사용해 변수로 처리할 데이터 속성 목록을 정의할 수 있습니다. 나머지 속성들은 자동으로 속성 백($attributes)에 담깁니다.

기본값을 설정하려면 프로퍼티 이름을 배열 키로, 기본값을 배열 값으로 지정합니다:

<!-- /resources/views/components/alert.blade.php --> @props(['type' => 'info', 'message']) <div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}> {{ $message }} </div>

이 컴포넌트를 다음과 같이 사용합니다:

<x-alert type="error" :message="$message" class="mb <h1 id="anonymous-index-components">Blade 템플릿</h1> <h2 id="data-properties-attributes">목차</h2> - [소개](#conditional-classes) - [Livewire로 Blade 확장하기](#component-attributes) - [데이터 출력](#component-attributes) --- <h2 id="accessing-parent-data">소개</h2> Blade는 Laravel에 기본 내장된 간결하면서도 강력한 템플릿 엔진입니다. 일부 PHP 템플릿 엔진과 달리, Blade는 템플릿 안에서 순수 PHP 코드를 자유롭게 사용할 수 있습니다. 실제로 모든 Blade 템플릿은 일반 PHP 코드로 컴파일된 뒤 캐시되며, 파일이 변경될 때만 다시 컴파일됩니다. 덕분에 Blade를 사용해도 애플리케이션 성능에 사실상 영향이 없습니다. Blade 템플릿 파일은 `.blade.php` 확장자를 사용하며, 일반적으로 `resources/views` 디렉터리에 저장합니다. Blade 뷰는 라우트나 컨트롤러에서 전역 `view` 헬퍼 함수를 통해 반환할 수 있습니다. [뷰 문서](/docs/10.x/routing/views)에서 설명한 것처럼, `view` 헬퍼의 두 번째 인자로 데이터를 전달할 수 있습니다. ```php Route::get('/', function () { return view('greeting', ['name' => '길동']); });

Livewire로 Blade 확장하기

Blade 템플릿을 한 단계 더 발전시켜 동적인 UI를 손쉽게 구현하고 싶다면 Laravel Livewire를 살펴보세요. Livewire를 사용하면 React나 Vue 같은 프론트엔드 프레임워크에서나 가능했던 동적 기능을 Blade 컴포넌트에 그대로 추가할 수 있습니다. 복잡한 클라이언트 사이드 렌더링이나 별도의 빌드 과정 없이 현대적인 반응형 프론트엔드를 구축할 수 있는 훌륭한 접근 방식입니다.

데이터 표시

Blade 뷰에 전달된 데이터는 이중 중괄호({{ }})로 감싸서 출력할 수 있습니다. 예를 들어 다음과 같은 라우트가 있다면:

Route::get('/', function () { return view('welcome', ['name' => '지수']); });

뷰에서 name 변수를 다음과 같이 출력할 수 있습니다:

안녕하세요, {{ $name }}.

NOTE

Blade의 {{ }} 출력 구문은 PHP의 htmlspecialchars 함수를 자동으로 통과하므로 XSS 공격을 방지합니다.

{{ }} 안에는 변수뿐만 아니라 어떤 PHP 표현식도 사용할 수 있습니다:

현재 UNIX 타임스탬프: {{ time() }}

HTML 엔티티 인코딩

기본적으로 Blade(그리고 Laravel의 e 헬퍼 함수)는 HTML 엔티티를 이중 인코딩합니다. 이중 인코딩을 비활성화하려면 AppServiceProviderboot 메서드에서 Blade::withoutDoubleEncoding을 호출하세요:

<?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::withoutDoubleEncoding(); } }

이스케이프 없이 데이터 출력하기

{{ }} 구문은 XSS 방지를 위해 항상 HTML 이스케이프를 적용합니다. 이스케이프 없이 원본 HTML을 그대로 출력하려면 {!! !!} 구문을 사용하세요:

안녕하세요, {!! $name !!}.

WARNING

사용자가 입력한 데이터를 출력할 때 {!! !!}를 사용하면 XSS 공격에 노출될 수 있습니다. 사용자 입력값은 반드시 {{ }}를 사용해 이스케이프 처리하세요.

Blade와 JavaScript 프레임워크

Vue.js나 Alpine.js 등 많은 JavaScript 프레임워크도 중괄호({{ }})를 표현식 출력에 사용합니다. 이 경우 Blade가 해당 구문을 처리하지 않도록 @ 기호를 앞에 붙이면 됩니다:

<h1>Laravel</h1> 안녕하세요, @{{ name }}.

이 예시에서 Blade는 @ 기호만 제거하고 {{ name }}은 그대로 남겨두어 JavaScript 프레임워크가 처리할 수 있도록 합니다.

@ 기호는 Blade 디렉티브 자체를 이스케이프할 때도 사용할 수 있습니다:

{{-- Blade 템플릿 --}} @@if() <!-- HTML 출력 결과 --> @if()

JSON 렌더링

JavaScript 변수를 초기화하기 위해 배열을 JSON으로 출력해야 할 때, 기존 방식은 다음과 같습니다:

<script> var app = <?php echo json_encode($array); ?>; </script>

이보다 더 안전한 방법은 Illuminate\Support\Js::from 메서드를 사용하는 것입니다. 이 메서드는 json_encode와 동일한 인수를 받으며, HTML 따옴표 안에서도 안전하게 사용할 수 있도록 JSON을 적절히 이스케이프합니다. 반환값은 JSON.parse(...) JavaScript 구문으로, 주어진 배열이나 객체를 유효한 JavaScript 객체로 변환합니다:

<script> var app = {{ Illuminate\Support\Js::from($array) }}; </script>

최신 Laravel 애플리케이션 스켈레톤에는 Js 파사드가 포함되어 있어 더 간결하게 사용할 수 있습니다:

<script> var app = {{ Js::from($array) }}; </script>

WARNING

Js::from은 이미 존재하는 변수를 JSON으로 렌더링할 때만 사용하세요. Blade 템플릿은 정규 표현식 기반으로 동작하기 때문에, 복잡한 표현식을 전달하면 예기치 않은 오류가 발생할 수 있습니다.

`@verbatim` 디렉티브

템플릿의 넓은 영역에 걸쳐 JavaScript 변수를 출력해야 한다면, 매번 @를 붙이는 대신 해당 블록 전체를 @verbatim 디렉티브로 감쌀 수 있습니다:

@verbatim <div class="container"> 안녕하세요, {{ name }}. </div> @endverbatim

@verbatim 블록 안에서는 Blade가 어떠한 처리도 하지 않으므로, {{ }} 구문이 JavaScript 프레임워크에 그대로 전달됩니다.

Blade 디렉티브

템플릿 상속과 데이터 출력 외에도, Blade는 조건문·반복문 등 자주 쓰는 PHP 제어 구조를 간결하게 작성할 수 있는 디렉티브를 제공합니다. PHP 문법과 거의 동일하게 동작하므로 별도의 학습 부담 없이 바로 사용할 수 있습니다.

If 문

@if, @elseif, @else, @endif 디렉티브로 조건문을 작성합니다. PHP의 if 문과 동일하게 동작합니다.

@if (count($records) === 1) 레코드가 1개 있습니다! @elseif (count($records) > 1) 레코드가 여러 개 있습니다! @else 레코드가 없습니다! @endif

반대 조건을 표현할 때는 @unless를 사용하면 더 읽기 편합니다.

@unless (Auth::check()) 로그인하지 않은 상태입니다. @endunless

PHP의 isset()empty()에 대응하는 디렉티브도 제공합니다.

@isset($records) // $records가 정의되어 있고 null이 아닌 경우... @endisset @empty($records) // $records가 "비어 있는" 경우... @endempty

인증 디렉티브

@auth@guest 디렉티브로 현재 사용자의 인증 여부를 빠르게 확인할 수 있습니다.

@auth // 인증된 사용자에게만 표시... @endauth @guest // 비로그인(게스트) 사용자에게만 표시... @endguest

특정 인증 가드를 지정해서 확인할 수도 있습니다.

@auth('admin') // admin 가드로 인증된 사용자에게만 표시... @endauth @guest('admin') // admin 가드 기준으로 비로그인 사용자에게만 표시... @endguest

환경 디렉티브

프로덕션 환경에서만 렌더링할 내용이 있다면 @production을 사용합니다.

@production // 프로덕션 환경에서만 표시되는 내용... @endproduction

특정 환경을 직접 지정하려면 @env를 사용합니다. 여러 환경을 배열로 전달할 수도 있습니다.

@env('staging') // staging 환경에서 실행 중... @endenv @env(['staging', 'production']) // staging 또는 production 환경에서 실행 중... @endenv

섹션 디렉티브

템플릿 상속에서 특정 섹션에 내용이 있는지 확인하려면 @hasSection을 사용합니다.

@hasSection('navigation') <div class="pull-right"> @yield('navigation') </div> <div class="clearfix"></div> @endif

반대로 섹션에 내용이 없을 때를 처리하려면 @sectionMissing을 사용합니다.

@sectionMissing('navigation') <div class="pull-right"> @include('default-navigation') </div> @endif

세션 디렉티브

@session 디렉티브는 특정 세션 값이 존재하는지 확인합니다. 값이 존재하면 블록 안의 내용이 렌더링되며, $value 변수로 해당 세션 값을 출력할 수 있습니다.

@session('status') <div class="p-4 bg-green-100"> {{ $value }} </div> @endsession

Switch 문

@switch, @case, @break, @default, @endswitch 디렉티브로 switch 문을 작성합니다.

@switch($i) @case(1) 첫 번째 케이스... @break @case(2) 두 번째 케이스... @break @default 기본 케이스... @endswitch

반복문

Blade는 PHP의 반복문 구조에 대응하는 디렉티브를 제공합니다. 동작 방식은 PHP와 동일합니다.

@for ($i = 0; $i < 10; $i++) 현재 값: {{ $i }} @endfor @foreach ($users as $user) <p>사용자 ID: {{ $user->id }}</p> @endforeach @forelse ($users as $user) <li>{{ $user->name }}</li> @empty <p>사용자가 없습니다.</p> @endforelse @while (true) <p>무한 반복 중...</p> @endwhile

NOTE

@foreach 루프 안에서는 $loop 변수를 사용할 수 있습니다. 현재 반복이 첫 번째인지 마지막인지 등 유용한 정보를 제공합니다.

루프 안에서 현재 반복을 건너뛰거나 루프를 종료할 때는 @continue@break를 사용합니다.

@foreach ($users as $user) @if ($user->type == 1) @continue @endif <li>{{ $user->name }}</li> @if ($user->number == 5) @break @endif @endforeach

조건식을 디렉티브 선언 안에 직접 포함할 수도 있어 코드를 더 간결하게 작성할 수 있습니다.

@foreach ($users as $user) @continue($user->type == 1) <li>{{ $user->name }}</li> @break($user->number == 5) @endforeach

$loop 변수

@foreach 루프 안에서는 자동으로 $loop 변수를 사용할 수 있습니다. 현재 반복의 인덱스, 첫/마지막 여부 등 다양한 정보를 담고 있습니다.

@foreach ($users as $user) @if ($loop->first) 첫 번째 반복입니다. @endif @if ($loop->last) 마지막 반복입니다. @endif <p>사용자 ID: {{ $user->id }}</p> @endforeach

중첩 루프에서는 $loop->parent로 부모 루프의 $loop 변수에 접근할 수 있습니다.

@foreach ($users as $user) @foreach ($user->posts as $post) @if ($loop->parent->first) 부모 루프의 첫 번째 반복입니다. @endif @endforeach @endforeach

$loop 변수가 제공하는 전체 속성은 다음과 같습니다.

속성설명
$loop->index현재 반복의 인덱스 (0부터 시작)
$loop->iteration현재 반복 횟수 (1부터 시작)
$loop->remaining남은 반복 횟수
$loop->count반복 중인 배열의 전체 아이템 수
$loop->first첫 번째 반복 여부
$loop->last마지막 반복 여부
$loop->even짝수 번째 반복 여부
$loop->odd홀수 번째 반복 여부
$loop->depth현재 루프의 중첩 깊이
$loop->parent중첩 루프에서 부모 루프의 $loop 변수

조건부 클래스 & 스타일

@class 디렉티브는 조건에 따라 CSS 클래스 문자열을 동적으로 생성합니다. 배열의 키는 클래스명, 값은 적용 여부를 나타내는 불리언 표현식입니다. 숫자 키를 가진 항목은 조건 없이 항상 포함됩니다.

@php $isActive = false; $hasError = true; @endphp <span @class([ 'p-4', 'font-bold' => $isActive, 'text-gray-500' => ! $isActive, 'bg-red' => $hasError, ])></span> {{-- 위 코드는 아래와 같이 렌더링됩니다 --}} <span class="p-4 text-gray-500 bg-red"></span>

마찬가지로 @style 디렉티브로 인라인 CSS 스타일을 조건부로 적용할 수 있습니다.

@php $isActive = true; @endphp <span @style([ 'background-color: red', 'font-weight: bold' => $isActive, ])></span> {{-- 위 코드는 아래와 같이 렌더링됩니다 --}} <span style="background-color: red; font-weight: bold;"></span>

추가 HTML 속성 디렉티브

@checked 디렉티브는 체크박스의 checked 상태를 조건부로 출력합니다. 조건이 true이면 checked가 출력됩니다.

<input type="checkbox" name="active" value="active" @checked(old('active', $user->active)) />

@selected 디렉티브는 <select> 옵션의 선택 여부를 처리합니다.

<select name="version"> @foreach ($product->versions as $version) <option value="{{ $version }}" @selected(old('version') == $version)> {{ $version }} </option> @endforeach </select>

@disabled 디렉티브는 요소를 비활성화 상태로 만들 때 사용합니다.

<button type="submit" @disabled($errors->isNotEmpty())>제출</button>

@readonly 디렉티브는 입력 필드를 읽기 전용으로 설정합니다.

<input type="email" name="email" value="email@laravel.com" @readonly($user->isNotAdmin()) />

@required 디렉티브는 입력 필드를 필수 항목으로 표시합니다.

<input type="text" name="title" value="title" @required($user->isAdmin()) />

서브뷰 포함하기

NOTE

@include 디렉티브도 유용하지만, Blade 컴포넌트를 사용하면 데이터 바인딩과 속성 전달 등 더 많은 기능을 활용할 수 있습니다. 새로 작성하는 코드라면 컴포넌트를 우선적으로 고려해 보세요.

@include 디렉티브로 다른 Blade 뷰를 현재 뷰 안에 포함할 수 있습니다. 부모 뷰에서 사용 가능한 모든 변수는 포함된 뷰에도 자동으로 전달됩니다.

<div> @include('shared.errors') <form> <!-- 폼 내용 --> </form> </div>

포함된 뷰에 추가 데이터를 배열로 전달할 수도 있습니다.

@include('view.name', ['status' => 'complete'])

존재하지 않는 뷰를 @include하면 Laravel이 오류를 발생시킵니다. 뷰가 있을 수도 없을 수도 있는 상황이라면 @includeIf를 사용하세요.

@includeIf('view.name', ['status' => 'complete'])

불리언 조건에 따라 뷰를 포함하거나 제외하려면 @includeWhen@includeUnless를 사용합니다.

@includeWhen($boolean, 'view.name', ['status' => 'complete']) @includeUnless($boolean, 'view.name', ['status' => 'complete'])

배열로 전달된 뷰 목록 중 존재하는 첫 번째 뷰를 포함하려면 @includeFirst를 사용합니다.

@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])

WARNING

Blade 뷰 안에서 __DIR__이나 __FILE__ 상수를 사용하지 마세요. 이 상수들은 실제 뷰 파일이 아닌 컴파일된 캐시 파일의 경로를 반환합니다.

컬렉션을 뷰로 렌더링하기

@each 디렉티브를 사용하면 반복문과 뷰 포함을 한 줄로 처리할 수 있습니다.

@each('view.name', $jobs, 'job')

첫 번째 인자는 각 항목에 대해 렌더링할 뷰, 두 번째 인자는 반복할 배열 또는 컬렉션, 세 번째 인자는 뷰 안에서 현재 항목에 접근할 변수명입니다. 현재 반복의 배열 키는 뷰 안에서 key 변수로 접근할 수 있습니다.

네 번째 인자로 배열이 비어 있을 때 렌더링할 뷰를 지정할 수 있습니다.

@each('view.name', $jobs, 'job', 'view.empty')

WARNING

@each로 렌더링된 뷰는 부모 뷰의 변수를 상속받지 않습니다. 자식 뷰에서 부모 변수가 필요하다면 @foreach@include를 함께 사용하세요.

@once 디렉티브

@once 디렉티브는 렌더링 사이클 전체에서 딱 한 번만 실행될 템플릿 블록을 정의합니다. 예를 들어 루프 안에서 특정 컴포넌트를 반복 렌더링할 때, JavaScript는 첫 번째 렌더링 시에만 스택에 추가하고 싶을 때 유용합니다.

@once @push('scripts') <script> // 커스텀 JavaScript... </script> @endpush @endonce

@once@push, @prepend와 함께 자주 사용되기 때문에, 이를 합친 @pushOnce@prependOnce 디렉티브도 제공합니다.

@pushOnce('scripts') <script> // 커스텀 JavaScript... </script> @endPushOnce

순수 PHP 코드

뷰 안에 PHP 코드를 직접 작성해야 할 때는 @php 디렉티브를 사용합니다.

@php $counter = 1; @endphp

클래스를 임포트하는 용도라면 @use 디렉티브가 더 간결합니다.

@use('App\Models\Flight')

두 번째 인자로 별칭(alias)을 지정할 수도 있습니다.

@use('App\Models\Flight', 'FlightModel')

주석

Blade 주석은 HTML 주석과 달리 렌더링된 HTML 출력에 포함되지 않습니다. 외부에 노출하고 싶지 않은 메모를 남길 때 유용합니다.

{{-- 이 주석은 렌더링된 HTML에 포함되지 않습니다 --}}

컴포넌트

컴포넌트와 슬롯은 섹션, 레이아웃, 인클루드와 비슷한 역할을 하지만, 많은 개발자들이 컴포넌트·슬롯 방식을 더 직관적으로 느낍니다. 컴포넌트를 작성하는 방법은 크게 두 가지입니다: 클래스 기반 컴포넌트익명 컴포넌트.

클래스 기반 컴포넌트를 만들려면 make:component Artisan 명령어를 사용합니다. 예시로 간단한 Alert 컴포넌트를 만들어 보겠습니다. 생성된 컴포넌트 클래스는 app/View/Components 디렉터리에 위치합니다:

php artisan make:component Alert

make:component 명령어는 뷰 템플릿도 함께 생성합니다. 뷰 파일은 resources/views/components 디렉터리에 저장됩니다. 애플리케이션 내에서 작성하는 컴포넌트는 app/View/Componentsresources/views/components 디렉터리에서 자동으로 인식되므로, 별도의 등록 작업은 필요하지 않습니다.

하위 디렉터리에 컴포넌트를 만들 수도 있습니다:

php artisan make:component Forms/Input

위 명령어는 app/View/Components/Forms 디렉터리에 Input 컴포넌트 클래스를, resources/views/components/forms 디렉터리에 뷰 파일을 생성합니다.

클래스 없이 Blade 템플릿만으로 구성된 익명 컴포넌트를 만들고 싶다면 --view 플래그를 사용합니다:

php artisan make:component forms.input --view

이 명령어는 resources/views/components/forms/input.blade.php 파일을 생성하며, <x-forms.input /> 태그로 렌더링할 수 있습니다.

패키지 컴포넌트 수동 등록

일반 애플리케이션 개발 시에는 컴포넌트가 자동으로 인식되지만, 패키지를 개발하는 경우에는 컴포넌트 클래스와 HTML 태그 별칭을 직접 등록해야 합니다. 보통 패키지 서비스 프로바이더의 boot 메서드에서 등록합니다:

use Illuminate\Support\Facades\Blade; /** * 패키지 서비스 초기화 */ public function boot(): void { Blade::component('package-alert', Alert::class); }

등록 후에는 태그 별칭으로 컴포넌트를 사용할 수 있습니다:

<x-package-alert/>

componentNamespace 메서드를 사용하면 네임스페이스 기반으로 컴포넌트를 자동 로드할 수도 있습니다. 예를 들어 Nightshade 패키지에 Package\Views\Components 네임스페이스 아래 CalendarColorPicker 컴포넌트가 있다면:

use Illuminate\Support\Facades\Blade; /** * 패키지 서비스 초기화 */ public function boot(): void { Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); }

이렇게 하면 패키지명:: 문법으로 컴포넌트를 사용할 수 있습니다:

<x-nightshade::calendar /> <x-nightshade::color-picker />

Blade는 컴포넌트 이름을 PascalCase로 변환하여 해당 클래스를 자동으로 찾습니다. 하위 디렉터리는 점(.) 표기법으로 지원됩니다.

컴포넌트 렌더링

Blade 템플릿 안에서 컴포넌트를 표시하려면 x- 접두사에 컴포넌트 클래스 이름을 kebab-case로 붙인 태그를 사용합니다:

<x-alert/> <x-user-profile/>

컴포넌트 클래스가 app/View/Components 안의 하위 디렉터리에 있다면 .으로 경로를 표현합니다. 예를 들어 app/View/Components/Inputs/Button.php에 위치한 컴포넌트는 다음과 같이 렌더링합니다:

<x-inputs.button/>

조건부로 컴포넌트를 렌더링하고 싶다면 컴포넌트 클래스에 shouldRender 메서드를 정의합니다. 이 메서드가 false를 반환하면 컴포넌트는 렌더링되지 않습니다:

use Illuminate\Support\Str; /** * 컴포넌트를 렌더링할지 여부를 반환합니다. */ public function shouldRender(): bool { return Str::length($this->message) > 0; }

컴포넌트에 데이터 전달

HTML 속성을 통해 컴포넌트에 데이터를 전달할 수 있습니다. 고정된 원시 값은 일반 HTML 속성 문자열로 전달하고, PHP 표현식이나 변수는 : 접두사를 붙여 전달합니다:

<x-alert type="error" :message="$message"/>

컴포넌트의 데이터 속성은 클래스 생성자에서 정의합니다. public 프로퍼티는 자동으로 뷰에서 사용 가능하므로, render 메서드에서 별도로 전달할 필요가 없습니다:

<?php namespace App\View\Components; use Illuminate\View\Component; use Illuminate\View\View; class Alert extends Component { /** * 컴포넌트 인스턴스를 생성합니다. */ public function __construct( public string $type, public string $message, ) {} /** * 컴포넌트를 나타내는 뷰를 반환합니다. */ public function render(): View { return view('components.alert'); } }

뷰 템플릿에서는 public 변수를 바로 출력할 수 있습니다:

<div class="alert alert-{{ $type }}"> {{ $message }} </div>

대소문자 규칙

컴포넌트 생성자의 인자는 camelCase로 정의하고, HTML 속성에서는 kebab-case로 전달합니다:

/** * 컴포넌트 인스턴스를 생성합니다. */ public function __construct( public string $alertType, ) {}
<x-alert alert-type="danger" />

속성 단축 문법

변수명과 속성명이 동일한 경우, 단축 문법을 사용하면 편리합니다:

{{-- 단축 문법 --}} <x-profile :$userId :$name /> {{-- 위 코드와 동일합니다 --}} <x-profile :user-id="$userId" :name="$name" />

속성 렌더링 이스케이프

Alpine.js처럼 콜론(:) 접두사 속성을 사용하는 JavaScript 프레임워크와 함께 사용할 때, 이중 콜론(::)을 사용하면 해당 속성이 PHP 표현식이 아님을 Blade에 알릴 수 있습니다:

<x-button ::class="{ danger: isDeleting }"> Submit </x-button>

위 코드는 다음과 같이 렌더링됩니다:

<button :class="{ danger: isDeleting }"> Submit </button>

컴포넌트 메서드

컴포넌트 클래스의 public 메서드도 뷰 템플릿에서 호출할 수 있습니다. 예를 들어 isSelected 메서드가 있다면:

/** * 주어진 옵션이 현재 선택된 옵션인지 확인합니다. */ public function isSelected(string $option): bool { return $option === $this->selected; }

뷰에서는 메서드 이름과 동일한 변수로 호출합니다:

<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}"> {{ $label }} </option>

컴포넌트 클래스 내에서 속성과 슬롯 접근

render 메서드에서 클로저를 반환하면, 컴포넌트 이름·속성·슬롯 등에 접근할 수 있습니다. 클로저는 $data 배열을 인자로 받습니다:

use Closure; /** * 컴포넌트를 나타내는 뷰를 반환합니다. */ public function render(): Closure { return function (array $data) { // $data['componentName']; // $data['attributes']; // $data['slot']; return '<div>컴포넌트 내용</div>'; }; }

componentNamex- 이후의 태그 이름입니다. 예를 들어 <x-alert />componentNamealert입니다. attributes는 HTML 태그에 전달된 모든 속성을 담고 있으며, slot은 슬롯 내용을 담은 Illuminate\Support\HtmlString 인스턴스입니다.

클로저가 반환한 문자열이 기존 뷰 이름과 일치하면 해당 뷰가 렌더링되고, 그렇지 않으면 인라인 Blade 뷰로 평가됩니다.

추가 의존성 주입

컴포넌트가 서비스 컨테이너의 의존성을 필요로 한다면, 생성자에서 데이터 속성보다 앞에 선언하면 컨테이너가 자동으로 주입해 줍니다:

use App\Services\AlertCreator; /** * 컴포넌트 인스턴스를 생성합니다. */ public function __construct( public AlertCreator $creator, public string $type, public string $message, ) {}

속성 / 메서드 숨기기

특정 public 프로퍼티나 메서드를 뷰 템플릿에 노출하고 싶지 않다면, $except 배열에 추가합니다:

<?php namespace App\View\Components; use Illuminate\View\Component; class Alert extends Component { /** * 컴포넌트 템플릿에 노출하지 않을 프로퍼티 / 메서드 목록 * * @var array */ protected $except = ['type']; /** * 컴포넌트 인스턴스를 생성합니다. */ public function __construct( public string $type, ) {} }

컴포넌트 속성

데이터 속성 외에도, class처럼 컴포넌트 동작과 무관한 HTML 속성을 추가로 전달해야 할 때가 있습니다. 이런 속성들은 보통 컴포넌트 템플릿의 루트 요소에 전달됩니다. 예를 들어:

<x-alert type="error" :message="$message" class="mt-4"/>

컴포넌트 생성자에 정의되지 않은 속성들은 자동으로 "속성 백(attribute bag)"에 모입니다. 이 백은 $attributes 변수로 뷰에서 사용할 수 있습니다:

<div {{ $attributes }}> <!-- 컴포넌트 내용 --> </div>

WARNING

현재 컴포넌트 태그 내에서 @env와 같은 디렉티브 사용은 지원되지 않습니다. 예를 들어 <x-alert :live="@env('production')"/>는 정상적으로 컴파일되지 않습니다.

기본값 설정 및 속성 병합

속성에 기본값을 지정하거나 값을 병합하고 싶을 때는 속성 백의 merge 메서드를 사용합니다. CSS 클래스 기본값을 지정할 때 특히 유용합니다:

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}> {{ $message }} </div>

이 컴포넌트를 다음과 같이 사용하면:

<x-alert type="error" :message="$message" class="mb-4"/>

최종 렌더링 결과는 다음과 같습니다:

<div class="alert alert-error mb-4"> <!-- $message 변수의 내용 --> </div>

클래스 조건부 병합

특정 조건이 true일 때만 클래스를 추가하고 싶다면 class 메서드를 사용합니다. 배열 키가 클래스명이고 값이 조건(boolean)입니다. 숫자 키를 가진 요소는 항상 포함됩니다:

<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}> {{ $message }} </div>

다른 속성도 함께 병합하려면 merge를 체이닝합니다:

<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}> {{ $slot }} </button>

NOTE

속성 백이 필요 없는 일반 HTML 요소에서 조건부 클래스를 처리하려면 @class 디렉티브를 사용하세요.

class 이외의 속성 병합

class가 아닌 속성은 merge에 지정한 값이 "기본값"이 되며, 외부에서 같은 속성을 전달하면 기본값이 덮어씌워집니다 (병합이 아닙니다). 예를 들어 button 컴포넌트가 다음과 같이 작성되어 있다면:

<button {{ $attributes->merge(['type' => 'button']) }}> {{ $slot }} </button>

컴포넌트 사용 시 type을 지정하면 해당 값이 사용되고, 지정하지 않으면 기본값 button이 사용됩니다:

<x-button type="submit"> Submit </x-button>

렌더링 결과:

<button type="submit"> Submit </button>

기본값과 외부 주입 값을 이어붙이고 싶다면 prepends 메서드를 사용합니다. 아래 예시에서 data-controller는 항상 profile-controller로 시작하며, 외부에서 추가 값을 전달하면 그 뒤에 붙습니다:

<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}> {{ $slot }} </div>

속성 조회 및 필터링

filter 메서드로 속성을 필터링할 수 있습니다. 클로저가 true를 반환하는 속성만 남습니다:

{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}

특정 문자열로 시작하는 키의 속성만 가져오려면 whereStartsWith를 사용합니다:

{{ $attributes->whereStartsWith('wire:model') }}

반대로 특정 문자열로 시작하는 속성을 제외하려면 whereDoesntStartWith를 사용합니다:

{{ $attributes->whereDoesntStartWith('wire:model') }}

첫 번째 속성만 렌더링하려면 first를 사용합니다:

{{ $attributes->whereStartsWith('wire:model')->first() }}

특정 속성이 존재하는지 확인하려면 has 메서드를 사용합니다:

@if ($attributes->has('class')) <div>class 속성이 있습니다</div> @endif

배열을 전달하면 나열된 속성이 모두 존재하는지 확인합니다:

@if ($attributes->has(['name', 'class'])) <div>모든 속성이 있습니다</div> @endif

나열된 속성 중 하나라도 존재하는지 확인하려면 hasAny를 사용합니다:

@if ($attributes->hasAny(['href', ':href', 'v-bind:href'])) <div>속성 중 하나가 있습니다</div> @endif

특정 속성 값을 가져오려면 get 메서드를 사용합니다:

{{ $attributes->get('class') }}

예약어

다음 키워드들은 Blade가 컴포넌트를 렌더링하는 데 내부적으로 사용하므로, 컴포넌트의 public 프로퍼티나 메서드 이름으로 사용할 수 없습니다:

  • data
  • render
  • resolveView
  • shouldRender
  • view
  • withAttributes
  • withName

슬롯

컴포넌트에 추가 콘텐츠를 전달할 때는 "슬롯"을 사용합니다. 슬롯 내용은 $slot 변수를 출력해서 표시합니다. 예를 들어 alert 컴포넌트가 다음과 같이 작성되어 있다면:

<!-- /resources/views/components/alert.blade.php --> <div class="alert alert-danger"> {{ $slot }} </div>

컴포넌트 태그 안에 내용을 넣어 슬롯으로 전달합니다:

<x-alert> <strong>이런!</strong> 문제가 발생했습니다! </x-alert>

여러 슬롯이 필요하다면 이름 있는 슬롯을 사용합니다. 아래는 title 슬롯을 추가한 예시입니다:

<!-- /resources/views/components/alert.blade.php --> <span class="alert-title">{{ $title }}</span> <div class="alert alert-danger"> {{ $slot }} </div>

x-slot 태그로 이름 있는 슬롯의 내용을 정의합니다. x-slot 태그 밖의 내용은 기본 $slot 변수로 전달됩니다:

<x-alert> <x-slot:title> 서버 오류 </x-slot> <strong>이런!</strong> 문제가 발생했습니다! </x-alert>

슬롯에 내용이 있는지 확인하려면 isEmpty 메서드를 사용합니다:

<span class="alert-title">{{ $title }}</span> <div class="alert alert-danger"> @if ($slot->isEmpty()) 슬롯이 비어 있을 때 표시되는 기본 내용입니다. @else {{ $slot }} @endif </div>

HTML 주석을 제외한 실제 콘텐츠가 있는지 확인하려면 hasActualContent를 사용합니다:

@if ($slot->hasActualContent()) 슬롯에 주석이 아닌 실제 내용이 있습니다. @endif

스코프 슬롯

Vue.js의 스코프 슬롯처럼, 슬롯 내부에서 컴포넌트의 데이터나 메서드에 접근하고 싶다면 $component 변수를 사용합니다. 예를 들어 x-alert 컴포넌트 클래스에 formatAlert 메서드가 있다면:

<x-alert> <x-slot:title> {{ $component->formatAlert('서버 오류') }} </x-slot> <strong>이런!</strong> 문제가 발생했습니다! </x-alert>

슬롯 속성

슬롯에도 CSS 클래스 등의 속성을 지정할 수 있습니다:

<x-card class="shadow-sm"> <x-slot:heading class="font-bold"> 제목 </x-slot> 본문 내용 <x-slot:footer class="text-sm"> 푸터 </x-slot> </x-card>

슬롯의 속성은 해당 슬롯 변수의 attributes 프로퍼티로 접근합니다. 속성 조작 방법은 컴포넌트 속성 문서를 참고하세요:

@props([ 'heading', 'footer', ]) <div {{ $attributes->class(['border']) }}> <h1 {{ $heading->attributes->class(['text-lg']) }}> {{ $heading }} </h1> {{ $slot }} <footer {{ $footer->attributes->class(['text-gray-700']) }}> {{ $footer }} </footer> </div>

인라인 컴포넌트 뷰

매우 작은 컴포넌트는 클래스 파일과 별도의 뷰 파일을 관리하기 번거로울 수 있습니다. 이런 경우 render 메서드에서 마크업을 직접 반환할 수 있습니다:

/** * 컴포넌트를 나타내는 뷰를 반환합니다. */ public function render(): string { return <<<'blade' <div class="alert alert-danger"> {{ $slot }} </div> blade; }

인라인 뷰 컴포넌트 생성

인라인 뷰 컴포넌트를 생성하려면 make:component 명령어에 --inline 옵션을 사용합니다:

php artisan make:component Alert --inline

동적 컴포넌트

어떤 컴포넌트를 렌더링할지 런타임에 결정해야 할 때는 dynamic-component를 사용합니다:

{{-- $componentName = "secondary-button" --}} <x-dynamic-component :component="$componentName" class="mt-4" />

컴포넌트 수동 등록

WARNING

아래 내용은 주로 Blade 컴포넌트를 포함한 Laravel 패키지를 개발하는 경우에 해당합니다. 일반 애플리케이션 개발자라면 이 섹션은 생략해도 됩니다.

패키지에 Blade 컴포넌트를 포함하거나 컴포넌트를 비표준 디렉터리에 배치하는 경우, 컴포넌트 클래스와 HTML 태그 별칭을 수동으로 등록해야 합니다. 패키지 서비스 프로바이더의 boot 메서드에서 등록하는 것이 일반적입니다:

use Illuminate\Support\Facades\Blade; use VendorPackage\View\Components\AlertComponent; /** * 패키지 서비스 초기화 */ public function boot(): void { Blade::component('package-alert', AlertComponent::class); }

등록 후에는 태그 별칭으로 사용할 수 있습니다:

<x-package-alert/>

패키지 컴포넌트 자동 로드

componentNamespace 메서드를 사용하면 네임스페이스 기반으로 컴포넌트를 자동 로드할 수도 있습니다. 예를 들어 Nightshade 패키지의 Package\Views\Components 네임스페이스 아래 CalendarColorPicker 컴포넌트가 있다면:

use Illuminate\Support\Facades\Blade; /** * 패키지 서비스 초기화 */ public function boot(): void { Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); }

이렇게 하면 패키지명:: 문법으로 컴포넌트를 사용할 수 있습니다:

<x-nightshade::calendar /> <x-nightshade::color-picker />

Blade는 컴포넌트 이름을 PascalCase로 변환하여 해당 클래스를 자동으로 찾습니다. 하위 디렉터리는 점(.) 표기법으로 지원됩니다.

익명 컴포넌트

익명 컴포넌트는 인라인 컴포넌트와 비슷하지만, 별도의 클래스 파일 없이 Blade 템플릿 파일 하나만으로 컴포넌트를 정의합니다. 사용 방법은 간단합니다. resources/views/components 디렉터리에 Blade 파일을 두기만 하면 됩니다.

예를 들어, resources/views/components/alert.blade.php 파일을 만들었다면 다음과 같이 렌더링할 수 있습니다.

<x-alert/>

컴포넌트가 components 디렉터리 하위 폴더에 있을 경우 . 문자로 경로를 나타냅니다. 예를 들어 resources/views/components/inputs/button.blade.php는 다음과 같이 렌더링합니다.

<x-inputs.button/>

익명 인덱스 컴포넌트

하나의 컴포넌트가 여러 Blade 템플릿으로 구성될 때, 관련 파일들을 하나의 디렉터리로 묶고 싶을 수 있습니다. 예를 들어 "accordion" 컴포넌트를 다음과 같은 구조로 만든다고 가정해 봅시다.

/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php

이 구조에서는 아래처럼 렌더링할 수 있습니다.

<x-accordion> <x-accordion.item> ... </x-accordion.item> </x-accordion>

그런데 이 방식에서는 accordion의 루트 템플릿(accordion.blade.php)을 accordion/ 디렉터리 밖에 둬야 한다는 점이 불편합니다. 관련 파일들을 하나의 디렉터리 안에 모두 넣고 싶을 때를 위해, Blade는 index.blade.php 파일을 지원합니다.

컴포넌트 디렉터리 안에 index.blade.php 파일이 있으면, Blade는 이 파일을 해당 컴포넌트의 "루트 노드"로 인식합니다. 위 예시에서 디렉터리 구조를 다음처럼 변경해도 동일하게 동작합니다.

/resources/views/components/accordion/index.blade.php
/resources/views/components/accordion/item.blade.php

렌더링 방식은 이전과 동일합니다.

<x-accordion> <x-accordion.item> ... </x-accordion.item> </x-accordion>

데이터 프로퍼티 / 속성

익명 컴포넌트에는 연결된 클래스가 없으므로, 어떤 값이 변수로 전달되고 어떤 값이 속성 백(attribute bag)으로 처리될지 구분할 방법이 필요합니다.

Blade 템플릿 최상단에 @props 디렉티브를 사용하면, 해당 항목들은 변수로 취급되고 나머지는 모두 속성 백으로 전달됩니다. 기본값을 설정하려면 배열의 키-값 형태로 지정합니다.

<!-- /resources/views/components/alert.blade.php --> @props(['type' => 'info', 'message']) <div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}> {{ $message }} </div>

위 컴포넌트는 다음과 같이 사용할 수 있습니다.

<x-alert type="error" :message="$message" class="mb-4"/>

부모 컴포넌트 데이터 접근

자식 컴포넌트 안에서 부모 컴포넌트의 데이터를 참조해야 할 때가 있습니다. 이런 경우 @aware 디렉티브를 사용합니다.

예를 들어 부모 <x-menu>와 자식 <x-menu.item>으로 구성된 메뉴 컴포넌트를 만든다고 가정해 봅시다.

<x-menu color="purple"> <x-menu.item>...</x-menu.item> <x-menu.item>...</x-menu.item> </x-menu>

<x-menu> 컴포넌트는 다음과 같이 구현할 수 있습니다.

<!-- /resources/views/components/menu/index.blade.php --> @props(['color' => 'gray']) <ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}> {{ $slot }} </ul>

color prop은 부모인 <x-menu>에만 전달되었기 때문에 <x-menu.item> 안에서는 기본적으로 접근할 수 없습니다. 그러나 @aware 디렉티브를 사용하면 부모로부터 전달된 값을 자식에서도 참조할 수 있습니다.

<!-- /resources/views/components/menu/item.blade.php --> @aware(['color' => 'gray']) <li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}> {{ $slot }} </li>

WARNING

@aware 디렉티브는 부모 컴포넌트에 HTML 속성으로 명시적으로 전달된 값에만 접근할 수 있습니다. 부모의 @props에 정의된 기본값이라도, 실제로 속성으로 전달되지 않은 값은 @aware로 접근할 수 없습니다.

익명 컴포넌트 경로 등록

익명 컴포넌트는 기본적으로 resources/views/components 디렉터리에서 찾습니다. 그러나 패키지 개발이나 모듈 구조를 사용할 때는 추가적인 경로를 등록해야 할 수 있습니다.

Blade::anonymousComponentPath() 메서드를 사용하면 됩니다. 첫 번째 인수로 컴포넌트 파일이 위치한 경로를, 선택적으로 두 번째 인수에 네임스페이스 접두사를 지정할 수 있습니다. 이 메서드는 일반적으로 서비스 프로바이더boot 메서드에서 호출합니다.

/** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::anonymousComponentPath(__DIR__.'/../components'); }

접두사 없이 경로를 등록하면, 해당 경로의 컴포넌트도 접두사 없이 바로 사용할 수 있습니다. 예를 들어 위 경로에 panel.blade.php가 있다면 다음처럼 렌더링합니다.

<x-panel />

네임스페이스 접두사가 필요하다면 두 번째 인수로 지정합니다.

Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');

접두사를 지정하면, 컴포넌트 이름 앞에 해당 네임스페이스를 붙여서 사용합니다.

<x-dashboard::panel />

Blade 템플릿

레이아웃 만들기

컴포넌트를 이용한 레이아웃

대부분의 웹 애플리케이션은 여러 페이지에 걸쳐 동일한 전체 레이아웃을 사용합니다. 모든 뷰마다 레이아웃 HTML을 반복해서 작성한다면 유지보수가 매우 번거로워질 것입니다. 다행히 레이아웃을 하나의 Blade 컴포넌트로 정의해두면, 애플리케이션 전체에서 손쉽게 재사용할 수 있습니다.

레이아웃 컴포넌트 정의하기

예를 들어, 할 일(todo) 목록 애플리케이션을 만든다고 가정해 보겠습니다. 아래와 같이 layout 컴포넌트를 정의할 수 있습니다.

<!-- resources/views/components/layout.blade.php --> <html> <head> <title>{{ $title ?? '할 일 관리' }}</title> </head> <body> <h1>할 일 목록</h1> <hr/> {{ $slot }} </body> </html>

레이아웃 컴포넌트 사용하기

layout 컴포넌트를 정의했으면, 이를 활용하는 Blade 뷰를 만들 수 있습니다. 아래는 할 일 목록을 출력하는 간단한 뷰입니다.

<!-- resources/views/tasks.blade.php --> <x-layout> @foreach ($tasks as $task) {{ $task }} @endforeach </x-layout>

컴포넌트 태그 사이에 작성한 내용은 layout 컴포넌트 내부의 기본 $slot 변수로 전달됩니다. 앞서 살펴봤듯이 layout 컴포넌트는 $title 슬롯도 지원하며, 슬롯이 제공되지 않으면 기본 타이틀이 표시됩니다. 컴포넌트 문서에서 설명하는 슬롯 문법을 사용해 커스텀 타이틀을 전달할 수 있습니다.

<!-- resources/views/tasks.blade.php --> <x-layout> <x-slot:title> 나만의 타이틀 </x-slot> @foreach ($tasks as $task) {{ $task }} @endforeach </x-layout>

레이아웃과 할 일 목록 뷰를 정의했다면, 라우트에서 해당 뷰를 반환하기만 하면 됩니다.

use App\Models\Task; Route::get('/tasks', function () { return view('tasks', ['tasks' => Task::all()]); });

템플릿 상속을 이용한 레이아웃

레이아웃 정의하기

레이아웃은 "템플릿 상속" 방식으로도 만들 수 있습니다. 이 방식은 컴포넌트가 도입되기 이전부터 사용해온 전통적인 방법입니다.

먼저 간단한 예시를 살펴보겠습니다. 아래는 페이지 레이아웃으로 사용할 Blade 뷰입니다.

<!-- resources/views/layouts/app.blade.php --> <html> <head> <title>앱 이름 - @yield('title')</title> </head> <body> @section('sidebar') 기본 사이드바 내용입니다. @show <div class="container"> @yield('content') </div> </body> </html>

일반적인 HTML 마크업과 함께 @section@yield 디렉티브가 사용된 것을 확인할 수 있습니다. @section은 콘텐츠 영역(섹션)을 정의하고, @yield는 해당 섹션의 내용을 출력합니다.

NOTE

@section@show를 함께 쓰면 섹션을 정의하는 동시에 즉시 출력합니다. 반면 자식 뷰에서 사용하는 @endsection은 섹션을 정의만 할 뿐, 출력은 부모 레이아웃의 @yield가 담당합니다. 이 차이를 헷갈리기 쉬우니 주의하세요.

레이아웃 상속하기

자식 뷰를 정의할 때는 @extends 디렉티브로 어떤 레이아웃을 상속할지 지정합니다. 자식 뷰는 @section 디렉티브를 사용해 레이아웃의 각 섹션에 콘텐츠를 주입하고, 주입된 내용은 레이아웃의 @yield 위치에 출력됩니다.

<!-- resources/views/child.blade.php --> @extends('layouts.app') @section('title', '페이지 타이틀') @section('sidebar') @@parent <p>기본 사이드바에 이 내용이 추가됩니다.</p> @endsection @section('content') <p>본문 콘텐츠입니다.</p> @endsection

이 예시에서 sidebar 섹션은 @@parent 디렉티브를 사용해 부모 레이아웃의 사이드바 내용을 덮어쓰지 않고 이어붙입니다. @@parent는 뷰가 렌더링될 때 부모 레이아웃의 해당 섹션 내용으로 대체됩니다.

@yield 디렉티브는 두 번째 인자로 기본값을 받을 수 있습니다. 해당 섹션이 자식 뷰에서 정의되지 않은 경우 이 기본값이 출력됩니다.

@yield('content', '기본 콘텐츠')

CSRF 필드

애플리케이션에서 HTML 폼을 정의할 때는 반드시 숨겨진 CSRF 토큰 필드를 포함해야 합니다. 그래야 CSRF 보호 미들웨어가 요청을 올바르게 검증할 수 있습니다. @csrf Blade 디렉티브를 사용하면 토큰 필드를 간단하게 생성할 수 있습니다:

<form method="POST" action="/profile"> @csrf ... </form>

Method 필드

HTML 폼은 기본적으로 PUT, PATCH, DELETE 요청을 직접 전송할 수 없습니다. 이런 HTTP 메서드를 사용하려면 숨겨진 _method 필드를 추가해 HTTP 동사를 흉내 내야 합니다. @method Blade 디렉티브를 사용하면 이 필드를 손쉽게 만들 수 있습니다:

<form action="/foo/bar" method="POST"> @method('PUT') ... </form>

유효성 검사 오류

@error 디렉티브를 사용하면 특정 필드에 대한 유효성 검사 오류 메시지가 존재하는지 빠르게 확인할 수 있습니다. @error 디렉티브 내부에서는 $message 변수를 출력해 오류 메시지를 표시합니다:

<!-- /resources/views/post/create.blade.php --> <label for="title">게시글 제목</label> <input id="title" type="text" class="@error('title') is-invalid @enderror"> @error('title') <div class="alert alert-danger">{{ $message }}</div> @enderror

@error 디렉티브는 내부적으로 if 구문으로 컴파일되므로, @else 디렉티브를 함께 사용하면 해당 필드에 오류가 없을 때의 내용도 렌더링할 수 있습니다:

<!-- /resources/views/auth.blade.php --> <label for="email">이메일 주소</label> <input id="email" type="email" class="@error('email') is-invalid @else is-valid @enderror">

한 페이지에 여러 개의 폼이 있는 경우, @error 디렉티브의 두 번째 인자로 특정 오류 백의 이름을 전달해 원하는 폼의 유효성 검사 오류 메시지만 가져올 수 있습니다:

<!-- /resources/views/auth.blade.php --> <label for="email">이메일 주소</label> <input id="email" type="email" class="@error('email', 'login') is-invalid @enderror"> @error('email', 'login') <div class="alert alert-danger">{{ $message }}</div> @enderror

스택(Stacks)

Blade는 **이름이 지정된 스택(named stack)**에 콘텐츠를 쌓아두고, 다른 뷰나 레이아웃의 원하는 위치에서 한꺼번에 렌더링할 수 있는 기능을 제공합니다. 이 기능은 자식 뷰에서 필요한 JavaScript 파일을 레이아웃의 <head> 또는 <body> 끝부분에 모아서 출력할 때 특히 유용합니다.

@push('scripts') <script src="/example.js"></script> @endpush

특정 조건이 true일 때만 스택에 내용을 추가하고 싶다면 @pushIf 디렉티브를 사용하세요:

@pushIf($shouldPush, 'scripts') <script src="/example.js"></script> @endPushIf

스택에는 필요한 만큼 여러 번 내용을 추가할 수 있습니다. 쌓인 내용을 실제로 출력하려면 @stack 디렉티브에 스택 이름을 전달합니다:

<head> <!-- 헤드 콘텐츠 --> @stack('scripts') </head>

NOTE

@stack은 스택에 추가된 순서대로 내용을 출력합니다. 출력 위치는 레이아웃 파일에 @stack을 선언한 곳이며, 자식 뷰의 @push는 해당 위치에 내용을 모아줍니다.

스택의 맨 앞에 내용을 삽입하고 싶다면 @prepend 디렉티브를 사용하세요. @push로 추가된 내용보다 먼저 출력됩니다:

@push('scripts') {{-- 두 번째로 출력됩니다 --}} <script src="/example.js"></script> @endpush {{-- 이후 어딘가에서... --}} @prepend('scripts') {{-- 첫 번째로 출력됩니다 --}} <script src="/first.js"></script> @endprepend

위 예시에서 @stack('scripts')가 렌더링되면, @prepend로 추가한 내용이 먼저, @push로 추가한 내용이 그 뒤에 출력됩니다.

서비스 주입

@inject 디렉티브를 사용하면 Laravel 서비스 컨테이너에서 서비스를 직접 가져올 수 있습니다. 첫 번째 인자는 서비스가 담길 변수명이고, 두 번째 인자는 컨테이너에서 리졸브할 클래스 또는 인터페이스 이름입니다.

@inject('metrics', 'App\Services\MetricsService') <div> 이번 달 매출: {{ $metrics->monthlyRevenue() }} </div>

NOTE

@inject는 뷰 내부에서 서비스를 직접 꺼내 쓸 수 있어 편리하지만, 비즈니스 로직이 뷰에 과도하게 침투하지 않도록 주의하세요. 복잡한 데이터 가공은 컨트롤러나 뷰 컴포저에서 처리하고, @inject는 단순 유틸리티 서비스(예: 포맷팅, 설정 조회 등)에 제한적으로 사용하는 것이 좋습니다.

인라인 Blade 템플릿 렌더링

인라인 Blade 템플릿 렌더링

Blade 템플릿 문자열을 직접 HTML로 변환해야 할 때가 있습니다. 이럴 때는 Blade 파사드의 render 메서드를 사용하면 됩니다. 첫 번째 인자로 Blade 템플릿 문자열을, 두 번째 인자로 템플릿에 전달할 데이터 배열을 선택적으로 넘길 수 있습니다.

use Illuminate\Support\Facades\Blade; return Blade::render('안녕하세요, {{ $name }}님!', ['name' => '홍길동']);

인라인 Blade 템플릿은 렌더링 시 storage/framework/views 디렉터리에 임시 파일로 저장됩니다. 렌더링 후 이 임시 파일을 자동으로 삭제하려면 deleteCachedView 인자를 true로 지정하세요.

return Blade::render( '안녕하세요, {{ $name }}님!', ['name' => '홍길동'], deleteCachedView: true );

NOTE

임시 캐시 파일이 쌓이는 것이 걱정된다면 deleteCachedView: true 옵션을 활용하세요. 단, 매번 파일을 삭제하므로 동일한 템플릿을 반복적으로 렌더링하는 고빈도 환경에서는 성능에 영향을 줄 수 있습니다.

Blade 프래그먼트 렌더링

Turbohtmx 같은 프론트엔드 프레임워크를 사용할 때, HTTP 응답으로 Blade 템플릿의 일부분만 반환해야 하는 경우가 있습니다. Blade 프래그먼트(fragment) 기능을 사용하면 뷰 전체가 아닌 특정 조각만 응답으로 돌려줄 수 있습니다.

NOTE

htmx를 사용하는 경우, 서버가 페이지 전체가 아닌 HTML 일부만 반환해야 할 때 이 기능이 특히 유용합니다. 예를 들어 목록을 동적으로 갱신하거나, 특정 컴포넌트만 교체하는 시나리오에서 활용할 수 있습니다.

먼저 반환하고 싶은 부분을 @fragment@endfragment 디렉티브로 감쌉니다:

@fragment('user-list') <ul> @foreach ($users as $user) <li>{{ $user->name }}</li> @endforeach </ul> @endfragment

그런 다음 뷰를 렌더링할 때 fragment 메서드를 호출하여 해당 프래그먼트만 HTTP 응답에 포함하도록 지정합니다:

return view('dashboard', ['users' => $users])->fragment('user-list');

fragmentIf 메서드를 사용하면 조건에 따라 프래그먼트를 반환할지, 뷰 전체를 반환할지 결정할 수 있습니다. 조건이 false이면 뷰 전체가 반환됩니다:

return view('dashboard', ['users' => $users]) ->fragmentIf($request->hasHeader('HX-Request'), 'user-list');

fragmentsfragmentsIf 메서드를 사용하면 여러 프래그먼트를 한 번에 반환할 수 있습니다. 지정한 프래그먼트들은 순서대로 이어 붙여져(concatenate) 하나의 응답으로 반환됩니다:

view('dashboard', ['users' => $users]) ->fragments(['user-list', 'comment-list']); view('dashboard', ['users' => $users]) ->fragmentsIf( $request->hasHeader('HX-Request'), ['user-list', 'comment-list'] );

Blade 확장

커스텀 디렉티브

directive 메서드를 사용하면 나만의 Blade 디렉티브를 정의할 수 있습니다. Blade 컴파일러가 해당 디렉티브를 만나면, 등록된 콜백을 호출하고 디렉티브에 전달된 표현식을 인수로 넘겨줍니다.

아래 예시는 DateTime 인스턴스를 받아 날짜를 포맷하는 @datetime($var) 디렉티브를 정의합니다. AppServiceProviderboot 메서드에서 등록합니다:

<?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * 애플리케이션 서비스를 등록합니다. */ public function register(): void { // ... } /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::directive('datetime', function (string $expression) { return "<?php echo ($expression)->format('Y-m-d H:i'); ?>"; }); } }

위 예시에서 디렉티브에 전달된 표현식에 format 메서드를 체이닝하고 있습니다. 결과적으로 이 디렉티브가 생성하는 실제 PHP 코드는 다음과 같습니다:

<?php echo ($var)->format('Y-m-d H:i'); ?>

WARNING

Blade 디렉티브의 로직을 수정한 후에는 반드시 캐시된 Blade 뷰를 모두 삭제해야 합니다. view:clear Artisan 커맨드로 캐시를 삭제할 수 있습니다.

php artisan view:clear

커스텀 Echo 핸들러

Blade 템플릿에서 {{ $object }}와 같이 객체를 출력하려고 하면, 해당 객체의 __toString 매직 메서드가 호출됩니다. 그런데 서드파티 라이브러리의 클래스처럼 __toString 메서드를 직접 제어할 수 없는 경우도 있습니다.

이런 상황을 위해 Blade는 특정 타입의 객체를 위한 커스텀 Echo 핸들러를 등록할 수 있는 stringable 메서드를 제공합니다. 이 메서드는 클로저를 인수로 받으며, 클로저의 타입 힌트로 렌더링 대상 객체 타입을 지정합니다. 마찬가지로 AppServiceProviderboot 메서드에서 등록하면 됩니다:

use Illuminate\Support\Facades\Blade; use Money\Money; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::stringable(function (Money $money) { return $money->formatTo('ko_KR'); }); }

핸들러를 등록한 후에는 Blade 템플릿에서 해당 객체를 그대로 출력할 수 있습니다:

결제 금액: {{ $money }}

커스텀 If 문

단순한 조건 분기를 처리할 때 매번 커스텀 디렉티브를 직접 구현하는 것은 번거로울 수 있습니다. 이를 위해 Blade는 Blade::if 메서드를 제공합니다. 클로저만 정의하면 조건 디렉티브를 간단하게 만들 수 있습니다.

예를 들어, 애플리케이션에 설정된 기본 파일시스템 디스크를 확인하는 커스텀 조건을 정의해 보겠습니다. AppServiceProviderboot 메서드에서 다음과 같이 등록합니다:

use Illuminate\Support\Facades\Blade; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::if('disk', function (string $value) { return config('filesystems.default') === $value; }); }

커스텀 조건을 등록하고 나면 템플릿에서 아래와 같이 사용할 수 있습니다:

@disk('local') {{-- 로컬 디스크를 사용 중입니다... --}} @elsedisk('s3') {{-- S3 디스크를 사용 중입니다... --}} @else {{-- 다른 디스크를 사용 중입니다... --}} @enddisk @unlessdisk('local') {{-- 로컬 디스크를 사용하지 않습니다... --}} @enddisk

NOTE

Blade::ifdisk를 등록하면 @disk, @elsedisk, @unlessdisk, @enddisk 디렉티브가 자동으로 생성됩니다. 네이밍 규칙만 이해하면 다양한 조건 디렉티브를 손쉽게 만들 수 있습니다.

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 7월 2일