Laravel Blade Templates: Components, Slots, CRUD Views and Breeze Auth

January 13, 2024 · 7 min read

2026 UPDATE - LARAVEL 13
We’re excited to announce that we have updated all of our blog post examples to reflect the new Laravel 13 version! Our previous examples were based on Laravel 10, but with the release of Laravel 13, we wanted to ensure that our readers have access to the most up-to-date information and examples.
Laravel Breeze and Blade
When combined with Laravel Breeze, you get a simple yet effective starting point for authentication views and a clear example of Blade in action. Let’s dive into the basics of Laravel Blade and how it’s used within Breeze to create a seamless user experience.

What is Laravel Blade?
Blade is Laravel’s built-in templating engine, designed to provide developers with a convenient way to write HTML templates while also allowing the use of PHP. It offers an expressive syntax for extending layouts, displaying data, and constructing reusable components.
Blade Syntax and Directives
Blade templates use a mix of HTML and Blade directives – special tokens that Blade recognizes and converts into PHP code. Here are some of the fundamental directives:
@extends: Indicates that the template extends a layout.@sectionand@endsection: Define a section of content.@yield: Used in layouts to display the content of a section.@include: Includes another Blade file within the template.{{ }}: Echoes data, automatically escaping HTML entities for security.@if,@elseif,@else,@endif: Control structures for conditional statements.@foreach,@endforeach: Loops through a data array.
Blade Layouts
Blade allows you to create a master layout that serves as a template for your application’s look. Here’s a simple example of a layout:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Head Contents -->
</head>
<body>
<header>
<!-- Header Content -->
</header>
<main>
@yield('content')
</main>
<footer>
<!-- Footer Content -->
</footer>
</body>
</html>Using @yield('content'), you define a placeholder for where the content of your child views will be injected.
Blade Templates with Breeze
Laravel Breeze simplifies authentication by providing a minimal and clean starting point, including Blade views for login, registration, password reset, and email verification. Let’s see how Blade and Breeze work together.
To install Breeze, require it via Composer and then run the install command:
composer require laravel/breeze --dev
php artisan breeze:installAs of Laravel 13, php artisan breeze:install prompts you to choose a frontend stack. You can also pass the stack name directly. The four available stacks are:
blade- simple Blade views, no JavaScript framework, good for beginnersreact- React + Inertia.js, SPA experiencevue- Vue 3 + Inertia.js, SPA experiencelivewire- Livewire v3, reactive UI without writing JavaScript
Laravel 13 Breeze includes passkey support out of the box. After installation, users can register and log in using Face ID, fingerprint, or a hardware security key.
After installing Breeze, you’ll notice that the views are stored in resources/views/auth. Here’s an example of how a login form might look using Blade:
@extends('layouts.app')
@section('content')
<div>
<form method="POST" action="{{ route('login') }}">
@csrf
<!-- Email Input -->
<div>
<label for="email">Email</label>
<input id="email" type="email" name="email" required autocomplete="email" autofocus>
</div>
<!-- Password Input -->
<div>
<label for="password">Password</label>
<input id="password" type="password" name="password" required>
</div>
<!-- Submit Button -->
<div>
<button type="submit">Login</button>
</div>
</form>
</div>
@endsectionIn this template, we’re extending the app layout and defining the content section. We use Blade’s @csrf directive to include a CSRF token field in the form for security, and {{ route('login') }} to generate the URL to the login route.
The screenshot below shows the Breeze Profile Edit page where we can modify the user data.

Blade Components
Blade also supports components, which are reusable pieces of the user interface. Breeze uses components to keep views DRY (Don’t Repeat Yourself). For example, you might have a component for an input field:
<input {{ $attributes }}>
You can use this component in your forms like so:
<x-input id="email" type="email" name="email" required />
Blog Post Views
Let’s create some of the views for our blog app example.
View for Displaying All Posts
First, we need to create a new folder named posts inside of the resources/views folder. Then we create a new file index.blade.php.
@extends('layouts.app')
@section('title', 'All Blog Posts')
@section('content')
<h1>All Blog Posts</h1>
<ul>
@forelse ($posts as $post)
<li>
<a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a>
<p>{{ $post->created_at->toFormattedDateString() }}</p>
</li>
@empty
<li>No posts available.</li>
@endforelse
</ul>
@endsectionIn this view, we’re extending a main layout and then defining a section for the content. We loop through all the posts using @forelse, which also handles the case where there are no posts. Each post title is a link to the single post view.
View for Displaying a Single Post
We create another file in the same directory, show.blade.php:
@extends('layouts.app')
@section('title', $post->title)
@section('content')
<article>
<h1>{{ $post->title }}</h1>
<p>Published {{ $post->created_at->diffForHumans() }}</p>
<div>{{ $post->content }}</div>
{{-- Check if there are any tags --}}
@if($post->tags->isNotEmpty())
<p>Tags:</p>
<ul>
@foreach($post->tags as $tag)
<li>{{ $tag->name }}</li>
@endforeach
</ul>
@endif
</article>
<a href="{{ route('posts.index') }}">Back to all posts</a>
@endsectionIn the single post view, we show the post title, publication date, and content. The diffForHumans() will display the date as 5 days ago or 2 years ago depending on the time difference between the current moment and the post date.
The screenshot below displays the Single Post page of a randomly selected post that we tested.

View for Creating a New Post
We create another file create.blade.php for displaying the form to create a new post.
@extends('layouts.app')
@section('title', 'Create New Post')
@section('content')
<h1>Create New Post</h1>
<form method="POST" action="{{ route('posts.store') }}">
@csrf
<div>
<label for="title">Title</label>
<input type="text" id="title" name="title" value="{{ old('title') }}" required>
@error('title')
<p>{{ $message }}</p>
@enderror
</div>
<div>
<label for="content">Content</label>
<textarea id="content" name="content" required>{{ old('content') }}</textarea>
@error('content')
<p>{{ $message }}</p>
@enderror
</div>
<div>
<label for="tags">Tags</label>
<select name="tags[]" id="tags" multiple>
@foreach (\App\Models\Tag::all() as $tag)
<option value="{{ $tag->id }}" {{ in_array($tag->id, old('tags', [])) ? 'selected' : '' }}>{{ $tag->name }}</option>
@endforeach
</select>
</div>
<div>
<button type="submit">Publish Post</button>
</div>
</form>
@endsectionIn the create post view, we have a form with fields for the post title and content. We use the @csrf directive to protect against cross-site request forgery. The @error directive is used to display validation errors for each field. The old() function is used to repopulate the fields with the previously entered data in case of a validation error.
Also, change function of the store in PostController:
// Store a newly created blog post
public function store(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
]);
$tags = $request->validate([
'tags' => 'required|array',
'tags.*' => 'exists:tags,id',
])['tags'];
// the new post belongs to the authenticated user
$post = auth()->user()->posts()->create($validatedData);
// we add tags to the post by using the sync() method
$post->tags()->sync($tags['tags']);
// PostPublished::dispatch($post);
return redirect()->route('posts.show', $post);
}Layout File
We will create a new folder in the views directory named as layouts. Then we will create a new file inside of that directory app.blade.php.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title') - My Blog</title>
</head>
<body>
<header>
<nav>
<a href="{{ url('/') }}">Home</a>
<a href="{{ route('posts.index') }}">Blog</a>
<a href="{{ route('posts.create') }}">Write a Post</a>
</nav>
</header>
<main>
@yield('content')
</main>
<footer>
<p>© My Blog</p>
</footer>
</body>
</html>The layout file provides a basic structure for the HTML pages, including a header with navigation links, a main content area where the individual views will be rendered, and a footer. The @yield directive is used to insert the content from the child views into the layout.
These Blade views provide the basic functionality for a blog to display posts and allow users to create new ones. You can customize the HTML and styling to match your blog’s design and requirements.

Anonymous Blade Components: The x- System
The x- component system (introduced in Laravel 7) is now the standard way to build reusable Blade UI. Anonymous components require no PHP class, just a Blade file in resources/views/components/.
Create resources/views/components/card.blade.php:
{{-- resources/views/components/card.blade.php --}}
<div class="border rounded-lg shadow p-4 bg-white">
@if(isset($header))
<div class="border-b pb-2 mb-4 font-bold text-lg">
{{ $header }}
</div>
@endif
<div>
{{ $slot }}
</div>
</div>Use it anywhere with the <x-card> tag. The content between the opening and closing tags becomes $slot. Named slots use <x-slot>:
{{-- Using the card component with a named slot --}}
<x-card>
<x-slot:header>Latest Posts</x-slot:header>
@forelse ($posts as $post)
<p>{{ $post->title }}</p>
@empty
<p>No posts yet.</p>
@endforelse
</x-card>Pass data as props using the colon syntax. Props declared in a @props directive become typed variables in the component:
{{-- resources/views/components/alert.blade.php --}}
@props(['type' => 'info', 'message'])
<div class="alert alert-{{ $type }}">
{{ $message }}
</div>{{-- Usage --}}
<x-alert type="success" message="Post created successfully!" />
<x-alert message="Something went wrong." type="error" />@stack and @push for page-specific assets
Not every page needs the same JavaScript. @stack and @push let child views inject scripts or styles into named slots in the layout, without adding them to every page.
First, add a @stack placeholder in your layout before the closing </body>:
{{-- resources/views/layouts/app.blade.php --}}
<body>
<main>
@yield('content')
</main>
{{-- Page-specific scripts go here --}}
@stack('scripts')
</body>Then from any child view, push content into that stack:
{{-- resources/views/posts/create.blade.php --}}
@extends('layouts.app')
@section('content')
{{-- form here --}}
@endsection
{{-- Only loads on the create post page --}}
@push('scripts')
<script>
// Initialize a date picker, character counter, etc.
console.log('Create post page loaded');
</script>
@endpushCompleting the Blog CRUD: Edit, Delete, and Flash Messages
Edit Post View
The edit view pre-populates form fields with the existing post data. Use old('field', $post->field) so that if validation fails, the form shows the user’s last input rather than the original value. Use @method('PUT') because HTML forms only support GET and POST:
{{-- resources/views/posts/edit.blade.php --}}
@extends('layouts.app')
@section('title', 'Edit: ' . $post->title)
@section('content')
<h1>Edit Post</h1>
<form method="POST" action="{{ route('posts.update', $post) }}">
@csrf
@method('PUT')
<div>
<label for="title">Title</label>
<input
type="text"
id="title"
name="title"
value="{{ old('title', $post->title) }}"
required
>
@error('title')
<p class="text-red-500">{{ $message }}</p>
@enderror
</div>
<div>
<label for="content">Content</label>
<textarea id="content" name="content" required>
{{ old('content', $post->content) }}
</textarea>
@error('content')
<p class="text-red-500">{{ $message }}</p>
@enderror
</div>
<button type="submit">Update Post</button>
</form>
@endsectionDelete Button
Deleting requires a POST form with @method('DELETE'). A small inline script adds a confirmation dialog so users do not accidentally delete posts:
{{-- Add to show.blade.php or index.blade.php --}}
@auth
@if(auth()->id() === $post->user_id)
<form
method="POST"
action="{{ route('posts.destroy', $post) }}"
onsubmit="return confirm('Are you sure you want to delete this post?')"
>
@csrf
@method('DELETE')
<button type="submit" class="text-red-500 hover:underline">
Delete Post
</button>
</form>
@endif
@endauthFlash Messages
Flash messages appear once after a redirect, perfect for "Post created successfully!" confirmations. Add the display code to your layout file so it works across all pages:
{{-- In resources/views/layouts/app.blade.php, inside <main> --}}
@if (session('success'))
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
{{ session('success') }}
</div>
@endif
@if (session('error'))
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{{ session('error') }}
</div>
@endif
@yield('content')Flash from any controller with:
return redirect()->route('posts.index')->with('success', 'Post created successfully!');
return redirect()->back()->with('error', 'Something went wrong.');Auth directives: @auth, @guest, and @can
Breeze sets up authentication for you, but you control what shows in Blade based on auth state using these directives:
{{-- Show different nav items based on auth state --}}
<nav>
<a href="{{ url('/') }}">Home</a>
<a href="{{ route('posts.index') }}">Blog</a>
@auth
{{-- Only visible to logged-in users --}}
<a href="{{ route('posts.create') }}">Write a Post</a>
<a href="{{ route('profile.edit') }}">Profile</a>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">Logout</button>
</form>
@endauth
@guest
{{-- Only visible to guests (not logged in) --}}
<a href="{{ route('login') }}">Login</a>
<a href="{{ route('register') }}">Register</a>
@endguest
</nav>Use @can when you have Laravel Policies set up and need finer-grained control (for example, only showing "Edit" to the post author):
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Edit Post</a>
@endcan
@can('delete', $post)
{{-- delete form here --}}
@endcan
@cannot('create', App\Models\Post::class)
<p>You do not have permission to create posts.</p>
@endcannotConclusion
Blade is a powerful templating engine that makes writing and managing your web application HTML a pleasure. Beyond layouts and basic directives, the x-component system gives you reusable UI building blocks, @stack and @push keep your JavaScript lean, and auth directives tie cleanly into Breeze authentication. With the full CRUD views covered here, you have everything you need to build a complete blog with Laravel and Blade.
Upcoming Articles in the Series
This article is part of our series Laravel for Beginners: A Step-by-Step Guide to Learn the Concepts.
Bring Your Ideas to Life 🚀
If you need help with a Laravel project let’s get in touch.
Lucky Media is proud to be recognized as a leading Laravel Development Agency
Technologies

Stay up-to-date
Be updated with all news, products and tips we share!
On this page
- 2026 UPDATE - LARAVEL 13
- Laravel Breeze and Blade
- What is Laravel Blade?
- Blog Post Views
- View for Displaying All Posts
- View for Displaying a Single Post
- View for Creating a New Post
- Layout File
- Anonymous Blade Components: The x- System
- @stack and @push for page-specific assets
- Completing the Blog CRUD: Edit, Delete, and Flash Messages
- Auth directives: @auth, @guest, and @can
- Conclusion
- Upcoming Articles in the Series
- Bring Your Ideas to Life 🚀

