Laravel Blade is a powerful templating engine that allows developers to create dynamic and reusable views in a Laravel application. One of the key features of Blade is the ability to create reusable and composable components, which can help speed up front-end development. By enabling the creation of reusable components that provide consistent styles and behaviour, developers can avoid the need to construct elements from scratch. Instead, they can simply make use of the components that already exist.
In this article we will create a basic form that shows you some benefits and techniques of blade components.
Creating your first component
There are two types of components: class-based and anonymous. Class-based components have a class and a view template, while anonymous components only have a view template. In most cases, anonymous components are sufficient, and I tend to use class-based components only when I need to use dependency injection.
php artisan make:component layouts.app --view
Running the above command will create our first component, a layout file named resources/views/components/layouts/app.blade.php. This file will be the layout of the app.
<!-- /resources/views/components/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel Blade Components</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
{{ $slot }}
</body>
</html>
<!-- /resources/views/example.blade.php -->
<x-layouts.app>
Hello World!
</x-layouts.app>
{{ $slot }}
will render whatever we pass to it. For example, passing Hello World! will render it on the page. Slots are what make blade components composable. We can pass components to other components and even use named slots!
Create a form
Run the following to create our anonymous index component
php artisan make:component form.index --view
Normally to group components together you would have to have a form component then have the rest of the forms components inside a folder.
/resources/views/components/form.blade.php
/resources/views/components/form/group.blade.php
Anonymous index components allow you to group your components together.
/resources/views/components/form/index.blade.php
/resources/views/components/form/group.blade.php
Usage would look like
<x-layouts.app>
<x-form>
<x-form.group>
Hello!
</xform.group>
</x-form>
</x-layouts.app>
<!-- /resources/views/components/form/index.blade.php -->
@props([
'method' => 'POST',
'action',
'hasFiles' => false,
])
<form
method="{{ $method !== 'GET' ? 'POST' : 'GET' }}"
action="{{ $action }}"
{!! $hasFiles ? 'enctype="multipart/form-data"' : '' !!}
{{ $attributes->except(['method', 'action']) }}
>
@csrf
@method($method)
{{ $slot }}
</form>
We define the props we want the component to accept. We are able to set some sensible defaults. Most forms have the method as POST
so we set that as default. When forms are sending files we need to have the enctype attribute. Instead of typing it out every time we make the component add this attribute on when we need it.
We want to make sure the developer passes an action when using this component so we set action as a prop but set no default value. When no action is passed Laravel will throw an error if we do not do an isset()
check.
Echoing out $attributes
allows us to pass any additional attributes we need without having to define them in the props.
<x-form action="/users"></x-form>
<x-form :action="route('users.store')"></x-form>
<x-form method="GET"></x-form>
<x-form has-files></x-form>
<x-form class="border border-red-200"></x-form>
<x-form
:action="route('photos.store')"
has-files
class="p-5"
>
//
</x-form>
In the above example we use the :action
attribute. We use the : prefix to denote that it is a PHP expression or variable. We could also do :action="$someRoute"
. If it is a hardcoded or primitive value we do action="/"
like normal.
Create text input
When working with inputs I like to create a group component that can take the input component. This group will display the label, help text and any errors.
php artisan make:component input.group --view
Here we are setting a default class using $attributes->class(['block'])
. If we were to pass the class attribute any classes would be merged with block.
We use @class()
to conditionally set classes. text-gray-700 inline-block mb-1
is always displayed, text-red-500
is only merged into the default when $error
is present.
If we don’t pass a prop in then it is not defined, that is why we use @isset($help)
and @isset($error)
to check if they are set. We could also set the props to have null
as their default value and check for that.
<!-- /resources/views/components/input/group.blade.php -->
@props([
'label',
'for',
'help',
'error',
])
<label
{{ $attributes }}
{{ $attributes->class(['block']) }}
for="{{ $for }}"
>
<span
@class([
'text-gray-700 inline-block mb-1',
'text-red-500' => isset($error)
])
>{{ $label ?? '' }}</span>
<div class="mt-1">
{{ $slot }}
</div>
@isset($help)
<p class="mt-2 text-sm text-gray-500" id="{{ $for }}">{{ $help }}</p>
@endif
@isset($error)
<div class="mt-1 text-red-500 text-sm">{{ $error }}</div>
@endif
</label>
Next we will create a text input
php artisan make:component input.text --view
This component uses @aware()
. This allows the child component to access data in the parent component.
<!-- /resources/views/components/input/text.blade.php -->
@aware([
'error',
])
@props([
'value',
'name',
'for',
])
<input
{{ $attributes->class([
'shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md',
'border-red-500' => $error
]) }}
@isset($name) name="{{ $name }}" @endif
type="text"
@isset($value) value="{{ $value }}" @endif
{{ $attributes }}
/>
Create button
Finally we can create a button to submit our form. We will use an anonymous index component again. This is because I don’t want to access the button directly, instead I want to use this component inside primary
, secondary
buttons.
php artisan make:component button.index --view
<!-- /resources/views/components/button/index.blade.php -->
@aware([
'type',
])
<button
type="{{ $type }}"
{{ $attributes->class([
'inline-flex items-center border font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2',
]) }}
>
{{ $slot }}
</button>
Next we create our primary
button. You can see that we are using the x-button
inside our primary
component.
php artisan make:component button.primary --view
<!-- /resources/views/components/button/primary.blade.php -->
@props([
'type' => 'button',
])
<x-button
{{ $attributes->merge(['class' => 'border-transparent shadow-sm text-white bg-indigo-600 hover:bg-indigo-700']) }}>
{{ $slot }}
</x-button>
Separating the x-button
component means that we can re-use it in multiple places, even using it on its own.
Putting it all together
Now we have created our components we can start to use them together. We first use our form component to wrap all our inputs, in this instance we only need to pass the route as the action. Next we have a couple of input groups, these take a label, for and an error. The error comes from the validation error bag that is available to all views. Passed to the input groups slot is the input. In this example we are using automatic attributes to pass along the old value to the value attribute. Lastly we use our button to submit the form.
<x-form :action="route('users.store')">
<x-input.group label="Email" for="email" :error="$errors->first('email')">
<x-input.email name="email" :value="old('email')" />
</x-input.group>
<x-input.group label="Password" for="password" :error="$errors->first('password')">
<x-input.password name="password" :value="old('password')" />
</x-input.group>
<x-button.primary>
Submit
</x-button.primary>
</x-form>
We can mix blade components with plain HTML as well.
<x-form action="/photo" has-files>
<div class="p-3 border border-slate-200">
<x-input.group label="Password" for="photo" :error="$errors->first('photo')">
<input type="file" name="photo" />
</x-input.group>
<button>Submit</button>
</div>
</x-form>
Bonus Tips
{{-- Short attribute syntax... --}}
<x-input.text $name />
{{-- Is equivalent to... --}}
<x-input.text name="{{ $name }}" />
Named Slots - You can have multiple slots in a component by giving them names.
<x-modal>
<x-slot:header>
Hello World!
</x-slot>
Some lovely content
<x-slot:footer>
Goodbye World!
</x-slot>
</x-modal>
You’d achieve this by doing the following
<!-- /resources/views/components/modal/index.blade.php -->
<div>
<header>
{{ $header }}
</header>
<main>
{{ $slot }}
</main>
<footer>
{{ $footer }}
</footer>
</div>
Top comments (2)
It is very clear. Thank you very much!
thank you.