File Organization
resources/views/
├── components/
│ ├── layouts/
│ │ ├── app.blade.php
│ │ └── auth.blade.php
│ ├── ui/
│ │ ├── button.blade.php
│ │ ├── modal.blade.php
│ │ └── card.blade.php
│ └── forms/
│ ├── input.blade.php
│ └── select.blade.php
├── pages/
│ ├── dashboard.blade.php
│ └── tickets/
│ ├── index.blade.php
│ └── show.blade.php
├── partials/
│ ├── header.blade.php
│ └── footer.blade.php
└── emails/
└── welcome.blade.php
Naming Conventions
- Use kebab-case for file names:
ticket-list.blade.php - Use kebab-case for component names:
<x-ui.button-group> - Group related components in subdirectories
// Good
<x-ui.button>
<x-forms.input>
<x-layouts.app>
// Bad
<x-Button>
<x-formInput>
<x-layouts_app>
Components
Use Component Syntax
Prefer the x- component syntax over @include:
// Good
<x-ui.card>
<x-slot:header>Card Title</x-slot:header>
Card content here
</x-ui.card>
// Bad
@include('partials.card', ['title' => 'Card Title', 'content' => 'Card content here'])
Pass Data via Attributes
// Good - Clear attribute binding
<x-ui.button type="submit" :disabled="$isProcessing">
Save
</x-ui.button>
// Good - Spread attributes
<x-forms.input :$name :$value :$errors />
// Bad - Passing data via @include
@include('components.button', ['type' => 'submit', 'disabled' => $isProcessing])
Component Class Definition
<?php
declare(strict_types=1);
namespace App\View\Components\Ui;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
final class Button extends Component
{
public function __construct(
public string $type = 'button',
public string $variant = 'primary',
public bool $disabled = false,
) {}
public function render(): View
{
return view('components.ui.button');
}
}
Slots
Named Slots
// Component definition (card.blade.php)
<div class="card">
@if(isset($header))
<div class="card-header">
{{ $header }}
</div>
@endif
<div class="card-body">
{{ $slot }}
</div>
@if(isset($footer))
<div class="card-footer">
{{ $footer }}
</div>
@endif
</div>
// Usage
<x-ui.card>
<x-slot:header>
<h2>Title</h2>
</x-slot:header>
<p>Main content goes here.</p>
<x-slot:footer>
<x-ui.button>Save</x-ui.button>
</x-slot:footer>
</x-ui.card>
Data Handling
Pass Data from Controllers
All data should be passed from controllers or Livewire components:
// Good - Data passed from controller
public function show(Ticket $ticket): View
{
return view('tickets.show', [
'ticket' => $ticket->load(['user', 'category']),
'statuses' => Status::cases(),
]);
}
// Bad - Query in Blade file
@foreach(App\Models\Ticket::all() as $ticket)
{{ $ticket->title }}
@endforeach
No Eloquent in Views
// Bad
{{ $user->tickets()->count() }}
{{ App\Models\Category::all() }}
@foreach($ticket->comments()->latest()->get() as $comment)
// Good - Pass computed data from controller
{{ $ticketCount }}
{{ $categories }}
@foreach($comments as $comment)
PHP Logic
Minimize PHP in Templates
// Bad - Complex logic in Blade
@php
$total = 0;
foreach ($items as $item) {
if ($item->is_active) {
$total += $item->price * $item->quantity;
}
}
@endphp
// Good - Compute in controller or model
{{ $order->formatted_total }}
Acceptable PHP Usage
// Simple formatting is acceptable
{{ $date->format('M j, Y') }}
{{ number_format($price, 2) }}
{{ Str::limit($description, 100) }}
Conditionals
Use Blade Directives
// Good
@if($user->isAdmin())
<x-admin-panel />
@endif
@unless($tickets->isEmpty())
<x-ticket-list :tickets="$tickets" />
@endunless
@auth
<x-user-menu :user="$user" />
@endauth
@guest
<x-login-button />
@endguest
// Avoid deeply nested conditionals - extract to components
Prefer Early Returns in Components
// In component class
public function shouldRender(): bool
{
return $this->items->isNotEmpty();
}
Loops
Use Blade Loop Variables
@foreach($items as $item)
<div @class([
'item',
'first' => $loop->first,
'last' => $loop->last,
])>
{{ $loop->iteration }}. {{ $item->name }}
</div>
@endforeach
@forelse($tickets as $ticket)
<x-ticket-card :ticket="$ticket" />
@empty
<x-empty-state message="No tickets found" />
@endforelse
Translations
Always Use Translation Helpers
// Good
<h1>{{ __('tickets.index.title') }}</h1>
<p>{{ __('tickets.show.created_by', ['name' => $user->name]) }}</p>
<x-ui.button>
{{ __('common.save') }}
</x-ui.button>
// Bad - Hardcoded strings
<h1>Tickets</h1>
<p>Created by {{ $user->name }}</p>
Translation Parameter Naming
Use camelCase for translation parameters:
// Good
{{ __('orders.total', ['itemCount' => $count, 'totalPrice' => $total]) }}
// Bad
{{ __('orders.total', ['item_count' => $count, 'total_price' => $total]) }}
Assets
Vite Directive
// In layout file
<!DOCTYPE html>
<html>
<head>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@stack('styles')
</head>
<body>
{{ $slot }}
@stack('scripts')
</body>
</html>
Asset Paths
// Good
<img src="{{ Vite::asset('resources/images/logo.png') }}" alt="Logo">
// For public assets
<img src="{{ asset('images/logo.png') }}" alt="Logo">
JavaScript
Use Stacks for Page-Specific Scripts
// In layout
<head>
@stack('head_scripts')
</head>
<body>
{{ $slot }}
@stack('footer_scripts')
</body>
// In page
@push('footer_scripts')
<script>
// Page-specific initialization
</script>
@endpush
Prefer External Files
// Good - External file
@vite('resources/js/pages/dashboard.js')
// Acceptable - Small inline Alpine.js
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">Content</div>
</div>
// Bad - Large inline scripts
<script>
// 50+ lines of JavaScript
</script>
Alpine.js for Interactivity
// Good - Alpine for simple interactions
<div x-data="{ showFilters: false }">
<button @click="showFilters = !showFilters">
{{ __('common.filters') }}
</button>
<div x-show="showFilters" x-collapse>
<x-filters :$filters />
</div>
</div>
CSS
Use Tailwind Classes
// Good - Tailwind utilities
<button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
Save
</button>
// Good - Extract repeated patterns to components
<x-ui.button variant="primary">Save</x-ui.button>
External Stylesheets for Custom CSS
/* resources/css/app.css */
@import './components/calendar.css';
@import './components/editor.css';
/* Small custom styles can go directly in app.css */
.prose-custom {
@apply prose prose-lg prose-blue;
}
Formatting
Attribute Order
// Recommended order
<x-component
{{-- Type/variant --}}
type="submit"
variant="primary"
{{-- State --}}
:disabled="$isLoading"
wire:loading.attr="disabled"
{{-- Styling --}}
class="mt-4"
>
Multi-line Attributes
// Good - Readable multi-line
<x-forms.input
name="email"
type="email"
:value="old('email')"
:error="$errors->first('email')"
required
/>
// Bad - Long single line
<x-forms.input name="email" type="email" :value="old('email')" :error="$errors->first('email')" required />