Windy Logo

Windy — Turn any HTML to Tailwind CSS!

Go back to Blog

Blade components for your layout

Marcel Pociot

Blade Components for your Layout

In the latest blog post I showed you how Laravels Blade view components work and how you can make use of them. In this part of the series, I want to focus on how we can use these components throughout our application - starting with the very basic: your layout.

In a typical Laravel application, you might have a file called layouts/app.blade.php that all of your other views extend from.

Such a view could look like this:

<!DOCTYPE html>
<html>
    <head>
	    @yield('scripts')
    </head>
    <body>
	    @yield('content')
    </body>
</html>

And in your actual pages, you will extend that view and provide your content within the content section. Here's an example welcome.blade.php file:

@extends('layouts.app')

@section('scripts')
	<!-- Some JS and styles -->
@endsection

@section('content')
  <div>My Page content is here</div>
@endsection

So far so good, so let's see how we can make use of our newly learned Blade components to refactor this.

First of all, we are going to create a new component called "Layout":

php artisan make:component Layout

Next, we can take all of our existing layout view code and place it inside of our new layout component view:

<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
    <head>
	    @yield('scripts')
    </head>
    <body>
	    @yield('content')
    </body>
</html>

To make this work, the only thing we need to do in our welcome.blade.php file is, instead of extending our layout - we can just wrap all of the content inside of our new layout component, like this:

<x-layout>
	@section('scripts')
		<!-- Some JS and styles -->
	@endsection
	
	@section('content')
	  <div>My Page content is here</div>
	@endsection
</x-layout>

This is already pretty good, but as we have seen in the previous post, Laravel provides a $slot variable that automatically holds all of the content that was placed inside of the blade component tags. So lets go and change our layout to make use of the $slot variable, instead of yielding a content section:

<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
    <head>
	    @yield('scripts')
    </head>
    <body>
		  {{ $slot }}
    </body>
</html>

Instead of using the @section directive, we can now simply pass the data as is:

<x-layout>
	@section('scripts')
		<!-- Some JS and styles -->
	@endsection
	
  <div>My Page content is here</div>
</x-layout>

Alright - so we got rid of the content section and wrapped everything into our layout component. That's already pretty cool and I think this improves the readability of our view component. Because of the indentation it is immediately clear which layout is being used. This scripts section is still bothering me though. So let's see how we can get rid of this as well - and there are multiple options.

Named Slots

The first option that we have is to make use of a "named slot". We already discussed the $slot variable, that will automatically be populated with everything within the component HTML, but we can also manually specify a slot name. This can be done using the x-slot tag - which itself is sort of a pre-defined Blade component that comes out of the box with Laravel.

By passing a name property to our x-slot, we can make the data available within a variable with the same name as our name attribute.

Let's modify our layout component to make use of a new $scripts variable:

<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
    <head>
	    {{ $scripts }}
    </head>
    <body>
		  {{ $slot }}
    </body>
</html>

To pass this variable to our view, we can now pass it into a scripts slot:

<x-layout>
  <x-slot name="scripts">
		<!-- Some JS and styles -->
  </x-slot>
	
  <div>My Page content is here</div>
</x-layout>

Refresh the page, and you can see that everything works as expected. With this, we introduced an error though, as our $scripts variable is now mandatory within our layout - so if a view would not provide a scripts slot, you would get an error "Undefined variable: scripts".

While this works with a named slot, we no longer have the ability to append to the scripts section from within multiple of our views/components - because we don't have a real "section" anymore.

So what if we want to get rid of the @section directives and use a component for this instead? First of, lets get our @yield directive back into our layout component:

<!DOCTYPE html>
<html>
    <head>
	    @yield('scripts')
    </head>
    <body>
		  {{ $slot }}
    </body>
</html>

Alright - now lets see how we can make use of this section, without using a directive.

Renderless Blade components

When you create a custom Blade component, the class has a render method, that by default returns a string containing the name of the view for the given component:

namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    public function render()
    {
        return view('components.alert');
    }
}

There's another way of returning a view though, which is by returning a callable, that is then going to return the actual view. In this case, our component only holds the data/state that was provided for the component, but it doesn't actually return a view - which is great for our section use case.

So lets create a new view component called Section:

php artisan make:component Section

Now instead of returning a view, we are actually going to return a function - that will return the @section directive for us:

namespace App\View\Components;

use Illuminate\View\Component;

class Section extends Component
{
    public function render()
    {
        return function ($componentData) {
            return <<<BLADE
                @section("{$componentData['attributes']->get('name')}")
                    {$componentData['slot']}
                @endsection
            BLADE;
        };
    }
}

Alright - so lets break this render method down line by line.

The render method returns a callable with one parameter - an array holding the component data. This array has the following data and keys available:

  • componentName - The name for the component, in our case "section"
  • attributes - The Illuminate\View\ComponentAttributeBag instance that holds all of our component attributes
  • slot - A Illuminate\Support\HtmlString object with the string that was passed to the default slot

Inside of this callable, we are returning a string - this string is escaped using PHPs Heredoc Syntax.

Inside of this string, we can now return any Blade code that we want - so in our case, we are opening a @section directive, with the name attribute that was provided in the <x-section tag. Inside of that @section directive, we pass the slot of our component (and cast it to a string, as it's a HtmlString object). And last but not least, we close the section directive.

To make use of this renderless component, we can now rewrite our page view like this:

<x-layout>
  <x-section name="scripts">
	 <!-- Some JS and styles -->
  </x-section>
	
  <div>My Page content is here</div>
</x-layout>

Yay - we did it! We got rid of our ugly directive and have successfully refactored it to make use of a custom blade component.

Is this better than using the directives? You'll have to decide for yourself - but I hope that this inspired you to take a look at renderless Blade components, as well as how you can use components for your traditional layout extensions.