Laravel Blade Components
Tags: laravel, blade, php
Published: 2023-05-28 09:46:28

[Laravel Blade](https://laravel.com/docs/10.x/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](https://laravel.com/docs/10.x/blade#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](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/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](https://laravel.com/docs/10.x/blade#accessing-parent-data).

```
<!-- /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](https://laravel.com/docs/10.x/validation#working-with-error-messages) 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](https://laravel.com/docs/10.x/blade#short-attribute-syntax)

```
{{-- Short attribute syntax... --}}
<x-input.text $name />

{{-- Is equivalent to... --}}
<x-input.text name="{{ $name }}" />
```

[Named Slots](https://laravel.com/docs/10.x/blade#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>
```